#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: tteck (tteckster) | MickLesk | michelroegl-brunner
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/branch/main/LICENSE

# ==============================================================================
# BUILD.FUNC - LXC CONTAINER BUILD & CONFIGURATION
# ==============================================================================
#
# This file provides the main build functions for creating and configuring
# LXC containers in Proxmox VE. It handles:
#
#   - Variable initialization and defaults
#   - Container creation and resource allocation
#   - Storage selection and management
#   - Advanced configuration and customization
#   - User interaction menus and prompts
#
# Usage:
#   - Sourced automatically by CT creation scripts
#   - Requires core.func and error_handler.func to be loaded first
#
# ==============================================================================

# ==============================================================================
# SECTION 1: INITIALIZATION & CORE VARIABLES
# ==============================================================================

# ------------------------------------------------------------------------------
# variables()
#
# - Initializes core variables for container creation
# - Normalizes application name (NSAPP = lowercase, no spaces)
# - Builds installer filename (var_install)
# - Defines regex patterns for validation
# - Fetches Proxmox hostname and version
# - Generates unique session ID for tracking and logging
# - Captures app-declared resource defaults (CPU, RAM, Disk)
# ------------------------------------------------------------------------------
variables() {
  NSAPP=$(echo "${APP,,}" | tr -d ' ')              # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
  var_install="${NSAPP}-install"                    # sets the var_install variable by appending "-install" to the value of NSAPP.
  INTEGER='^[0-9]+([.][0-9]+)?$'                    # it defines the INTEGER regular expression pattern.
  PVEHOST_NAME=$(hostname)                          # gets the Proxmox Hostname and sets it to Uppercase
  DIAGNOSTICS="yes"                                 # sets the DIAGNOSTICS variable to "yes", used for the API call.
  METHOD="default"                                  # sets the METHOD variable to "default", used for the API call.
  RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
  SESSION_ID="${RANDOM_UUID:0:8}"                   # Short session ID (first 8 chars of UUID) for log files
  BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log"     # Host-side container creation log
  CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"

  # Parse dev_mode early
  parse_dev_mode

  # Setup persistent log directory if logs mode active
  if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then
    mkdir -p /var/log/community-scripts
    BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log"
  fi

  # Get Proxmox VE version and kernel version
  if command -v pveversion >/dev/null 2>&1; then
    PVEVERSION="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
  else
    PVEVERSION="N/A"
  fi
  KERNEL_VERSION=$(uname -r)

  # Capture app-declared defaults (for precedence logic)
  # These values are set by the app script BEFORE default.vars is loaded
  # If app declares higher values than default.vars, app values take precedence
  if [[ -n "${var_cpu:-}" && "${var_cpu}" =~ ^[0-9]+$ ]]; then
    export APP_DEFAULT_CPU="${var_cpu}"
  fi
  if [[ -n "${var_ram:-}" && "${var_ram}" =~ ^[0-9]+$ ]]; then
    export APP_DEFAULT_RAM="${var_ram}"
  fi
  if [[ -n "${var_disk:-}" && "${var_disk}" =~ ^[0-9]+$ ]]; then
    export APP_DEFAULT_DISK="${var_disk}"
  fi
}

source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)

if command -v curl >/dev/null 2>&1; then
  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
  load_functions
  catch_errors
elif command -v wget >/dev/null 2>&1; then
  source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
  source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
  load_functions
  catch_errors
fi

# ==============================================================================
# SECTION 2: PRE-FLIGHT CHECKS & SYSTEM VALIDATION
# ==============================================================================

# ------------------------------------------------------------------------------
# maxkeys_check()
#
# - Reads kernel keyring limits (maxkeys, maxbytes)
# - Checks current usage for LXC user (UID 100000)
# - Warns if usage is close to limits and suggests sysctl tuning
# - Exits if thresholds are exceeded
# - https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html
# ------------------------------------------------------------------------------

maxkeys_check() {
  # Read kernel parameters
  per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0)
  per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0)

  # Exit if kernel parameters are unavailable
  if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then
    echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}"
    exit 1
  fi

  # Fetch key usage for user ID 100000 (typical for containers)
  used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0)
  used_lxc_bytes=$(awk '/100000:/ {split($5, a, "/"); print a[1]}' /proc/key-users 2>/dev/null || echo 0)

  # Calculate thresholds and suggested new limits
  threshold_keys=$((per_user_maxkeys - 100))
  threshold_bytes=$((per_user_maxbytes - 1000))
  new_limit_keys=$((per_user_maxkeys * 2))
  new_limit_bytes=$((per_user_maxbytes * 2))

  # Check if key or byte usage is near limits
  failure=0
  if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then
    echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}"
    echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
    failure=1
  fi
  if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then
    echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}"
    echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
    failure=1
  fi

  # Provide next steps if issues are detected
  if [[ "$failure" -eq 1 ]]; then
    echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}"
    exit 1
  fi

  # Silent success - only show errors if they exist
}

# ==============================================================================
# SECTION 3: CONTAINER SETUP UTILITIES
# ==============================================================================

# ------------------------------------------------------------------------------
# get_current_ip()
#
# - Returns current container IP depending on OS type
# - Debian/Ubuntu: uses `hostname -I`
# - Alpine: parses eth0 via `ip -4 addr`
# - Returns "Unknown" if OS type cannot be determined
# ------------------------------------------------------------------------------
get_current_ip() {
  if [ -f /etc/os-release ]; then
    # Check for Debian/Ubuntu (uses hostname -I)
    if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
      CURRENT_IP=$(hostname -I | awk '{print $1}')
    # Check for Alpine (uses ip command)
    elif grep -q 'ID=alpine' /etc/os-release; then
      CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
    else
      CURRENT_IP="Unknown"
    fi
  fi
  echo "$CURRENT_IP"
}

# ------------------------------------------------------------------------------
# update_motd_ip()
#
# - Updates /etc/motd with current container IP
# - Removes old IP entries to avoid duplicates
# ------------------------------------------------------------------------------
update_motd_ip() {
  MOTD_FILE="/etc/motd"

  if [ -f "$MOTD_FILE" ]; then
    # Remove existing IP Address lines to prevent duplication
    sed -i '/IP Address:/d' "$MOTD_FILE"

    IP=$(get_current_ip)
    # Add the new IP address
    echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE"
  fi
}

# ------------------------------------------------------------------------------
# install_ssh_keys_into_ct()
#
# - Installs SSH keys into container root account if SSH is enabled
# - Uses pct push or direct input to authorized_keys
# - Falls back to warning if no keys provided
# ------------------------------------------------------------------------------
install_ssh_keys_into_ct() {
  [[ "$SSH" != "yes" ]] && return 0

  if [[ -n "$SSH_KEYS_FILE" && -s "$SSH_KEYS_FILE" ]]; then
    msg_info "Installing selected SSH keys into CT ${CTID}"
    pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || {
      msg_error "prepare /root/.ssh failed"
      return 1
    }
    pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 ||
      pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || {
      msg_error "write authorized_keys failed"
      return 1
    }
    pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true
    msg_ok "Installed SSH keys into CT ${CTID}"
    return 0
  fi

  # Fallback
  msg_warn "No SSH keys to install (skipping)."
  return 0
}

# ------------------------------------------------------------------------------
# find_host_ssh_keys()
#
# - Scans system for available SSH keys
# - Supports defaults (~/.ssh, /etc/ssh/authorized_keys)
# - Returns list of files containing valid SSH public keys
# - Sets FOUND_HOST_KEY_COUNT to number of keys found
# ------------------------------------------------------------------------------
find_host_ssh_keys() {
  local re='(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))'
  local -a files=() cand=()
  local g="${var_ssh_import_glob:-}"
  local total=0 f base c

  shopt -s nullglob
  if [[ -n "$g" ]]; then
    for pat in $g; do cand+=($pat); done
  else
    cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)
    cand+=(/root/.ssh/*.pub)
    cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)
  fi
  shopt -u nullglob

  for f in "${cand[@]}"; do
    [[ -f "$f" && -r "$f" ]] || continue
    base="$(basename -- "$f")"
    case "$base" in
    known_hosts | known_hosts.* | config) continue ;;
    id_*) [[ "$f" != *.pub ]] && continue ;;
    esac

    # CRLF safe check for host keys
    c=$(tr -d '\r' <"$f" | awk '
      /^[[:space:]]*#/ {next}
      /^[[:space:]]*$/ {next}
      {print}
    ' | grep -E -c '"$re"' || true)

    if ((c > 0)); then
      files+=("$f")
      total=$((total + c))
    fi
  done

  # Fallback to /root/.ssh/authorized_keys
  if ((${#files[@]} == 0)) && [[ -r /root/.ssh/authorized_keys ]]; then
    if grep -E -q "$re" /root/.ssh/authorized_keys; then
      files+=(/root/.ssh/authorized_keys)
      total=$((total + $(grep -E -c "$re" /root/.ssh/authorized_keys || echo 0)))
    fi
  fi

  FOUND_HOST_KEY_COUNT="$total"
  (
    IFS=:
    echo "${files[*]}"
  )
}

# ==============================================================================
# SECTION 4: STORAGE & RESOURCE MANAGEMENT
# ==============================================================================

# ------------------------------------------------------------------------------
# _write_storage_to_vars()
#
# - Writes storage selection to vars file
# - Removes old entries (commented and uncommented) to avoid duplicates
# - Arguments: vars_file, key (var_container_storage/var_template_storage), value
# ------------------------------------------------------------------------------
_write_storage_to_vars() {
  # $1 = vars_file, $2 = key (var_container_storage / var_template_storage), $3 = value
  local vf="$1" key="$2" val="$3"
  # remove uncommented and commented versions to avoid duplicates
  sed -i "/^[#[:space:]]*${key}=/d" "$vf"
  echo "${key}=${val}" >>"$vf"
}

choose_and_set_storage_for_file() {
  # $1 = vars_file, $2 = class ('container'|'template')
  local vf="$1" class="$2" key="" current=""
  case "$class" in
  container) key="var_container_storage" ;;
  template) key="var_template_storage" ;;
  *)
    msg_error "Unknown storage class: $class"
    return 1
    ;;
  esac

  current=$(awk -F= -v k="^${key}=" '$0 ~ k {print $2; exit}' "$vf")

  # If only one storage exists for the content type, auto-pick. Else always ask (your wish #4).
  local content="rootdir"
  [[ "$class" == "template" ]] && content="vztmpl"
  local count
  count=$(pvesm status -content "$content" | awk 'NR>1{print $1}' | wc -l)

  if [[ "$count" -eq 1 ]]; then
    STORAGE_RESULT=$(pvesm status -content "$content" | awk 'NR>1{print $1; exit}')
    STORAGE_INFO=""
  else
    # If the current value is preselectable, we could show it, but per your requirement we always offer selection
    select_storage "$class" || return 1
  fi

  _write_storage_to_vars "$vf" "$key" "$STORAGE_RESULT"

  # Keep environment in sync for later steps (e.g. app-default save)
  if [[ "$class" == "container" ]]; then
    export var_container_storage="$STORAGE_RESULT"
    export CONTAINER_STORAGE="$STORAGE_RESULT"
  else
    export var_template_storage="$STORAGE_RESULT"
    export TEMPLATE_STORAGE="$STORAGE_RESULT"
  fi

  # Silent operation - no output message
}

# ==============================================================================
# SECTION 5: CONFIGURATION & DEFAULTS MANAGEMENT
# ==============================================================================

# ------------------------------------------------------------------------------
# base_settings()
#
# - Defines all base/default variables for container creation
# - Reads from environment variables (var_*)
# - Provides fallback defaults for OS type/version
# - App-specific values take precedence when they are HIGHER (for CPU, RAM, DISK)
# - Sets up container type, resources, network, SSH, features, and tags
# ------------------------------------------------------------------------------
base_settings() {
  # Default Settings
  CT_TYPE=${var_unprivileged:-"1"}

  # Resource allocation: App defaults take precedence if HIGHER
  # Compare app-declared values (saved in APP_DEFAULT_*) with current var_* values
  local final_disk="${var_disk:-4}"
  local final_cpu="${var_cpu:-1}"
  local final_ram="${var_ram:-1024}"

  # If app declared higher values, use those instead
  if [[ -n "${APP_DEFAULT_DISK:-}" && "${APP_DEFAULT_DISK}" =~ ^[0-9]+$ ]]; then
    if [[ "${APP_DEFAULT_DISK}" -gt "${final_disk}" ]]; then
      final_disk="${APP_DEFAULT_DISK}"
    fi
  fi

  if [[ -n "${APP_DEFAULT_CPU:-}" && "${APP_DEFAULT_CPU}" =~ ^[0-9]+$ ]]; then
    if [[ "${APP_DEFAULT_CPU}" -gt "${final_cpu}" ]]; then
      final_cpu="${APP_DEFAULT_CPU}"
    fi
  fi

  if [[ -n "${APP_DEFAULT_RAM:-}" && "${APP_DEFAULT_RAM}" =~ ^[0-9]+$ ]]; then
    if [[ "${APP_DEFAULT_RAM}" -gt "${final_ram}" ]]; then
      final_ram="${APP_DEFAULT_RAM}"
    fi
  fi

  DISK_SIZE="${final_disk}"
  CORE_COUNT="${final_cpu}"
  RAM_SIZE="${final_ram}"
  VERBOSE=${var_verbose:-"${1:-no}"}
  PW=${var_pw:-""}
  CT_ID=${var_ctid:-$NEXTID}
  HN=${var_hostname:-$NSAPP}
  BRG=${var_brg:-"vmbr0"}
  NET=${var_net:-"dhcp"}
  IPV6_METHOD=${var_ipv6_method:-"none"}
  IPV6_STATIC=${var_ipv6_static:-""}
  GATE=${var_gateway:-""}
  APT_CACHER=${var_apt_cacher:-""}
  APT_CACHER_IP=${var_apt_cacher_ip:-""}

  # Runtime check: Verify APT cacher is reachable if configured
  if [[ -n "$APT_CACHER_IP" && "$APT_CACHER" == "yes" ]]; then
    if ! curl -s --connect-timeout 2 "http://${APT_CACHER_IP}:3142" >/dev/null 2>&1; then
      msg_warn "APT Cacher configured but not reachable at ${APT_CACHER_IP}:3142"
      msg_custom "⚠️" "${YW}" "Disabling APT Cacher for this installation"
      APT_CACHER=""
      APT_CACHER_IP=""
    else
      msg_ok "APT Cacher verified at ${APT_CACHER_IP}:3142"
    fi
  fi

  MTU=${var_mtu:-""}
  SD=${var_storage:-""}
  NS=${var_ns:-""}
  MAC=${var_mac:-""}
  VLAN=${var_vlan:-""}
  SSH=${var_ssh:-"no"}
  SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""}
  UDHCPC_FIX=${var_udhcpc_fix:-""}
  TAGS="community-script,${var_tags:-}"
  ENABLE_FUSE=${var_fuse:-"${1:-no}"}
  ENABLE_TUN=${var_tun:-"${1:-no}"}

  # Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts
  if [ -z "$var_os" ]; then
    var_os="debian"
  fi
  if [ -z "$var_version" ]; then
    var_version="12"
  fi
}

# ------------------------------------------------------------------------------
# load_vars_file()
#
# - Safe parser for KEY=VALUE lines from vars files
# - Used by default_var_settings and app defaults loading
# - Only loads whitelisted var_* keys
# ------------------------------------------------------------------------------
load_vars_file() {
  local file="$1"
  [ -f "$file" ] || return 0
  msg_info "Loading defaults from ${file}"

  # Allowed var_* keys
  local VAR_WHITELIST=(
    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu var_keyctl
    var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
    var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
    var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
  )

  # Whitelist check helper
  _is_whitelisted() {
    local k="$1" w
    for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done
    return 1
  }

  local line key val
  while IFS= read -r line || [ -n "$line" ]; do
    line="${line#"${line%%[![:space:]]*}"}"
    line="${line%"${line##*[![:space:]]}"}"
    [[ -z "$line" || "$line" == \#* ]] && continue
    if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
      local var_key="${BASH_REMATCH[1]}"
      local var_val="${BASH_REMATCH[2]}"

      [[ "$var_key" != var_* ]] && continue
      _is_whitelisted "$var_key" || continue

      # Strip quotes
      if [[ "$var_val" =~ ^\"(.*)\"$ ]]; then
        var_val="${BASH_REMATCH[1]}"
      elif [[ "$var_val" =~ ^\'(.*)\'$ ]]; then
        var_val="${BASH_REMATCH[1]}"
      fi

      # Set only if not already exported
      [[ -z "${!var_key+x}" ]] && export "${var_key}=${var_val}"
    fi
  done <"$file"
  msg_ok "Loaded ${file}"
}

# ------------------------------------------------------------------------------
# default_var_settings
#
# - Ensures /usr/local/community-scripts/default.vars exists (creates if missing)
# - Loads var_* values from default.vars (safe parser, no source/eval)
# - Precedence: ENV var_* > default.vars > built-in defaults
# - Maps var_verbose → VERBOSE
# - Calls base_settings "$VERBOSE" and echo_default
# ------------------------------------------------------------------------------
default_var_settings() {
  # Allowed var_* keys (alphabetically sorted)
  # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)
  local VAR_WHITELIST=(
    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu var_keyctl
    var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
    var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
    var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
  )

  # Snapshot: environment variables (highest precedence)
  declare -A _HARD_ENV=()
  local _k
  for _k in "${VAR_WHITELIST[@]}"; do
    if printenv "$_k" >/dev/null 2>&1; then _HARD_ENV["$_k"]=1; fi
  done

  # Find default.vars location
  local _find_default_vars
  _find_default_vars() {
    local f
    for f in \
      /usr/local/community-scripts/default.vars \
      "$HOME/.config/community-scripts/default.vars" \
      "./default.vars"; do
      [ -f "$f" ] && {
        echo "$f"
        return 0
      }
    done
    return 1
  }
  # Allow override of storages via env (for non-interactive use cases)
  [ -n "${var_template_storage:-}" ] && TEMPLATE_STORAGE="$var_template_storage"
  [ -n "${var_container_storage:-}" ] && CONTAINER_STORAGE="$var_container_storage"

  # Create once, with storages already selected, no var_ctid/var_hostname lines
  local _ensure_default_vars
  _ensure_default_vars() {
    _find_default_vars >/dev/null 2>&1 && return 0

    local canonical="/usr/local/community-scripts/default.vars"
    # Silent creation - no msg_info output
    mkdir -p /usr/local/community-scripts

    # Pick storages before writing the file (always ask unless only one)
    # Create a minimal temp file to write into
    : >"$canonical"

    # Base content (no var_ctid / var_hostname here)
    cat >"$canonical" <<'EOF'
# Community-Scripts defaults (var_* only). Lines starting with # are comments.
# Precedence: ENV var_* > default.vars > built-ins.
# Keep keys alphabetically sorted.

# Container type
var_unprivileged=1

# Resources
var_cpu=1
var_disk=4
var_ram=1024

# Network
var_brg=vmbr0
var_net=dhcp
var_ipv6_method=none
# var_gateway=
# var_vlan=
# var_mtu=
# var_mac=
# var_ns=

# SSH
var_ssh=no
# var_ssh_authorized_key=

# APT cacher (optional - with example)
# var_apt_cacher=yes
# var_apt_cacher_ip=192.168.1.10

# Features/Tags/verbosity
var_fuse=no
var_tun=no

# Advanced Settings (Proxmox-official features)
var_nesting=1          # Allow nesting (required for Docker/LXC in CT)
var_keyctl=0           # Allow keyctl() - needed for Docker (systemd-networkd workaround)
var_mknod=0            # Allow device node creation (requires kernel 5.3+, experimental)
var_mount_fs=          # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults)
var_protection=no      # Prevent accidental deletion of container
var_timezone=          # Container timezone (e.g. Europe/Berlin, leave empty for host timezone)
var_tags=community-script
var_verbose=no

# Security (root PW) – empty => autologin
# var_pw=
EOF

    # Now choose storages (always prompt unless just one exists)
    choose_and_set_storage_for_file "$canonical" template
    choose_and_set_storage_for_file "$canonical" container

    chmod 0644 "$canonical"
    # Silent creation - no output message
  }

  # Whitelist check
  local _is_whitelisted_key
  _is_whitelisted_key() {
    local k="$1"
    local w
    for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done
    return 1
  }

  # 1) Ensure file exists
  _ensure_default_vars

  # 2) Load file
  local dv
  dv="$(_find_default_vars)" || {
    msg_error "default.vars not found after ensure step"
    return 1
  }
  load_vars_file "$dv"

  # 3) Map var_verbose → VERBOSE
  if [[ -n "${var_verbose:-}" ]]; then
    case "${var_verbose,,}" in 1 | yes | true | on) VERBOSE="yes" ;; 0 | no | false | off) VERBOSE="no" ;; *) VERBOSE="${var_verbose}" ;; esac
  else
    VERBOSE="no"
  fi

  # 4) Apply base settings and show summary
  METHOD="mydefaults-global"
  base_settings "$VERBOSE"
  header_info
  echo -e "${DEFAULT}${BOLD}${BL}Using User Defaults (default.vars) on node $PVEHOST_NAME${CL}"
  echo_default
}

# ------------------------------------------------------------------------------
# get_app_defaults_path()
#
# - Returns full path for app-specific defaults file
# - Example: /usr/local/community-scripts/defaults/<app>.vars
# ------------------------------------------------------------------------------

get_app_defaults_path() {
  local n="${NSAPP:-${APP,,}}"
  echo "/usr/local/community-scripts/defaults/${n}.vars"
}

# ------------------------------------------------------------------------------
# maybe_offer_save_app_defaults
#
# - Called after advanced_settings returned with fully chosen values.
# - If no <nsapp>.vars exists, offers to persist current advanced settings
#   into /usr/local/community-scripts/defaults/<nsapp>.vars
# - Only writes whitelisted var_* keys.
# - Extracts raw values from flags like ",gw=..." ",mtu=..." etc.
# ------------------------------------------------------------------------------
if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then
  # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)
  declare -ag VAR_WHITELIST=(
    var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_gpu
    var_gateway var_hostname var_ipv6_method var_mac var_mtu
    var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged
    var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
  )
fi

# Global whitelist check function (used by _load_vars_file_to_map and others)
_is_whitelisted_key() {
  local k="$1"
  local w
  for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done
  return 1
}

_sanitize_value() {
  # Disallow Command-Substitution / Shell-Meta
  case "$1" in
  *'$('* | *'`'* | *';'* | *'&'* | *'<('*)
    echo ""
    return 0
    ;;
  esac
  echo "$1"
}

# Map-Parser: read var_* from file into _VARS_IN associative array
# Note: Main _load_vars_file() with full validation is defined in default_var_settings section
# This simplified version is used specifically for diff operations via _VARS_IN array
declare -A _VARS_IN
_load_vars_file_to_map() {
  local file="$1"
  [ -f "$file" ] || return 0
  _VARS_IN=() # Clear array
  local line key val
  while IFS= read -r line || [ -n "$line" ]; do
    line="${line#"${line%%[![:space:]]*}"}"
    line="${line%"${line##*[![:space:]]}"}"
    [ -z "$line" ] && continue
    case "$line" in
    \#*) continue ;;
    esac
    key=$(printf "%s" "$line" | cut -d= -f1)
    val=$(printf "%s" "$line" | cut -d= -f2-)
    case "$key" in
    var_*)
      if _is_whitelisted_key "$key"; then
        _VARS_IN["$key"]="$val"
      fi
      ;;
    esac
  done <"$file"
}

# Diff function for two var_* files -> produces human-readable diff list for $1 (old) vs $2 (new)
_build_vars_diff() {
  local oldf="$1" newf="$2"
  local k
  local -A OLD=() NEW=()
  _load_vars_file_to_map "$oldf"
  for k in "${!_VARS_IN[@]}"; do OLD["$k"]="${_VARS_IN[$k]}"; done
  _load_vars_file_to_map "$newf"
  for k in "${!_VARS_IN[@]}"; do NEW["$k"]="${_VARS_IN[$k]}"; done

  local out
  out+="# Diff for ${APP} (${NSAPP})\n"
  out+="# Old: ${oldf}\n# New: ${newf}\n\n"

  local found_change=0

  # Changed & Removed
  for k in "${!OLD[@]}"; do
    if [[ -v NEW["$k"] ]]; then
      if [[ "${OLD[$k]}" != "${NEW[$k]}" ]]; then
        out+="~ ${k}\n    - old: ${OLD[$k]}\n    + new: ${NEW[$k]}\n"
        found_change=1
      fi
    else
      out+="- ${k}\n    - old: ${OLD[$k]}\n"
      found_change=1
    fi
  done

  # Added
  for k in "${!NEW[@]}"; do
    if [[ ! -v OLD["$k"] ]]; then
      out+="+ ${k}\n    + new: ${NEW[$k]}\n"
      found_change=1
    fi
  done

  if [[ $found_change -eq 0 ]]; then
    out+="(No differences)\n"
  fi

  printf "%b" "$out"
}

# Build a temporary <app>.vars file from current advanced settings
_build_current_app_vars_tmp() {
  tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)"

  # NET/GW
  _net="${NET:-}"
  _gate=""
  case "${GATE:-}" in
  ,gw=*) _gate=$(echo "$GATE" | sed 's/^,gw=//') ;;
  esac

  # IPv6
  _ipv6_method="${IPV6_METHOD:-auto}"
  _ipv6_static=""
  _ipv6_gateway=""
  if [ "$_ipv6_method" = "static" ]; then
    _ipv6_static="${IPV6_ADDR:-}"
    _ipv6_gateway="${IPV6_GATE:-}"
  fi

  # MTU/VLAN/MAC
  _mtu=""
  _vlan=""
  _mac=""
  case "${MTU:-}" in
  ,mtu=*) _mtu=$(echo "$MTU" | sed 's/^,mtu=//') ;;
  esac
  case "${VLAN:-}" in
  ,tag=*) _vlan=$(echo "$VLAN" | sed 's/^,tag=//') ;;
  esac
  case "${MAC:-}" in
  ,hwaddr=*) _mac=$(echo "$MAC" | sed 's/^,hwaddr=//') ;;
  esac

  # DNS / Searchdomain
  _ns=""
  _searchdomain=""
  case "${NS:-}" in
  -nameserver=*) _ns=$(echo "$NS" | sed 's/^-nameserver=//') ;;
  esac
  case "${SD:-}" in
  -searchdomain=*) _searchdomain=$(echo "$SD" | sed 's/^-searchdomain=//') ;;
  esac

  # SSH / APT / Features
  _ssh="${SSH:-no}"
  _ssh_auth="${SSH_AUTHORIZED_KEY:-}"
  _apt_cacher="${APT_CACHER:-}"
  _apt_cacher_ip="${APT_CACHER_IP:-}"
  _fuse="${ENABLE_FUSE:-no}"
  _tun="${ENABLE_TUN:-no}"
  _nesting="${ENABLE_NESTING:-1}"
  _keyctl="${ENABLE_KEYCTL:-0}"
  _mknod="${ENABLE_MKNOD:-0}"
  _mount_fs="${ALLOW_MOUNT_FS:-}"
  _protect="${PROTECT_CT:-no}"
  _timezone="${CT_TIMEZONE:-}"
  _tags="${TAGS:-}"
  _verbose="${VERBOSE:-no}"

  # Type / Resources / Identity
  _unpriv="${CT_TYPE:-1}"
  _cpu="${CORE_COUNT:-1}"
  _ram="${RAM_SIZE:-1024}"
  _disk="${DISK_SIZE:-4}"
  _hostname="${HN:-$NSAPP}"

  # Storage
  _tpl_storage="${TEMPLATE_STORAGE:-${var_template_storage:-}}"
  _ct_storage="${CONTAINER_STORAGE:-${var_container_storage:-}}"

  {
    echo "# App-specific defaults for ${APP} (${NSAPP})"
    echo "# Generated on $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    echo

    echo "var_unprivileged=$(_sanitize_value "$_unpriv")"
    echo "var_cpu=$(_sanitize_value "$_cpu")"
    echo "var_ram=$(_sanitize_value "$_ram")"
    echo "var_disk=$(_sanitize_value "$_disk")"

    [ -n "${BRG:-}" ] && echo "var_brg=$(_sanitize_value "$BRG")"
    [ -n "$_net" ] && echo "var_net=$(_sanitize_value "$_net")"
    [ -n "$_gate" ] && echo "var_gateway=$(_sanitize_value "$_gate")"
    [ -n "$_mtu" ] && echo "var_mtu=$(_sanitize_value "$_mtu")"
    [ -n "$_vlan" ] && echo "var_vlan=$(_sanitize_value "$_vlan")"
    [ -n "$_mac" ] && echo "var_mac=$(_sanitize_value "$_mac")"
    [ -n "$_ns" ] && echo "var_ns=$(_sanitize_value "$_ns")"

    [ -n "$_ipv6_method" ] && echo "var_ipv6_method=$(_sanitize_value "$_ipv6_method")"
    # var_ipv6_static removed - static IPs are unique, can't be default

    [ -n "$_ssh" ] && echo "var_ssh=$(_sanitize_value "$_ssh")"
    [ -n "$_ssh_auth" ] && echo "var_ssh_authorized_key=$(_sanitize_value "$_ssh_auth")"

    [ -n "$_apt_cacher" ] && echo "var_apt_cacher=$(_sanitize_value "$_apt_cacher")"
    [ -n "$_apt_cacher_ip" ] && echo "var_apt_cacher_ip=$(_sanitize_value "$_apt_cacher_ip")"

    [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")"
    [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")"
    [ -n "$_nesting" ] && echo "var_nesting=$(_sanitize_value "$_nesting")"
    [ -n "$_keyctl" ] && echo "var_keyctl=$(_sanitize_value "$_keyctl")"
    [ -n "$_mknod" ] && echo "var_mknod=$(_sanitize_value "$_mknod")"
    [ -n "$_mount_fs" ] && echo "var_mount_fs=$(_sanitize_value "$_mount_fs")"
    [ -n "$_protect" ] && echo "var_protection=$(_sanitize_value "$_protect")"
    [ -n "$_timezone" ] && echo "var_timezone=$(_sanitize_value "$_timezone")"
    [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")"
    [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")"

    [ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")"
    [ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")"

    [ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")"
    [ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")"
  } >"$tmpf"

  echo "$tmpf"
}

# ------------------------------------------------------------------------------
# maybe_offer_save_app_defaults()
#
# - Called after advanced_settings()
# - Offers to save current values as app defaults if not existing
# - If file exists: shows diff and allows Update, Keep, View Diff, or Cancel
# ------------------------------------------------------------------------------
maybe_offer_save_app_defaults() {
  local app_vars_path
  app_vars_path="$(get_app_defaults_path)"

  # always build from current settings
  local new_tmp diff_tmp
  new_tmp="$(_build_current_app_vars_tmp)"
  diff_tmp="$(mktemp -p /tmp "${NSAPP:-app}.vars.diff.XXXXXX")"

  # 1) if no file → offer to create
  if [[ ! -f "$app_vars_path" ]]; then
    if whiptail --backtitle "Proxmox VE Helper Scripts" \
      --yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then
      mkdir -p "$(dirname "$app_vars_path")"
      install -m 0644 "$new_tmp" "$app_vars_path"
      msg_ok "Saved app defaults: ${app_vars_path}"
    fi
    rm -f "$new_tmp" "$diff_tmp"
    return 0
  fi

  # 2) if file exists → build diff
  _build_vars_diff "$app_vars_path" "$new_tmp" >"$diff_tmp"

  # if no differences → do nothing
  if grep -q "^(No differences)$" "$diff_tmp"; then
    rm -f "$new_tmp" "$diff_tmp"
    return 0
  fi

  # 3) if file exists → show menu with default selection "Update Defaults"
  local app_vars_file
  app_vars_file="$(basename "$app_vars_path")"

  while true; do
    local sel
    sel="$(whiptail --backtitle "Proxmox VE Helper Scripts" \
      --title "APP DEFAULTS – ${APP}" \
      --menu "Differences detected. What do you want to do?" 20 78 10 \
      "Update Defaults" "Write new values to ${app_vars_file}" \
      "Keep Current" "Keep existing defaults (no changes)" \
      "View Diff" "Show a detailed diff" \
      "Cancel" "Abort without changes" \
      --default-item "Update Defaults" \
      3>&1 1>&2 2>&3)" || { sel="Cancel"; }

    case "$sel" in
    "Update Defaults")
      install -m 0644 "$new_tmp" "$app_vars_path"
      msg_ok "Updated app defaults: ${app_vars_path}"
      break
      ;;
    "Keep Current")
      msg_custom "ℹ️" "${BL}" "Keeping current app defaults: ${app_vars_path}"
      break
      ;;
    "View Diff")
      whiptail --backtitle "Proxmox VE Helper Scripts" \
        --title "Diff – ${APP}" \
        --scrolltext --textbox "$diff_tmp" 25 100
      ;;
    "Cancel" | *)
      msg_custom "🚫" "${YW}" "Canceled. No changes to app defaults."
      break
      ;;
    esac
  done

  rm -f "$new_tmp" "$diff_tmp"
}

ensure_storage_selection_for_vars_file() {
  local vf="$1"

  # Read stored values (if any)
  local tpl ct
  tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2-)
  ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2-)

  if [[ -n "$tpl" && -n "$ct" ]]; then
    TEMPLATE_STORAGE="$tpl"
    CONTAINER_STORAGE="$ct"
    return 0
  fi

  choose_and_set_storage_for_file "$vf" template
  choose_and_set_storage_for_file "$vf" container

  # Silent operation - no output message
}

ensure_global_default_vars_file() {
  local vars_path="/usr/local/community-scripts/default.vars"
  if [[ ! -f "$vars_path" ]]; then
    mkdir -p "$(dirname "$vars_path")"
    touch "$vars_path"
  fi
  echo "$vars_path"
}

# ==============================================================================
# SECTION 6: ADVANCED INTERACTIVE CONFIGURATION
# ==============================================================================

# ------------------------------------------------------------------------------
# advanced_settings()
#
# - Interactive wizard-style configuration with BACK navigation
# - State-machine approach: each step can go forward or backward
# - Cancel at Step 1 = Exit Script, Cancel at other steps = Go Back
# - Allows user to customize all container settings
# ------------------------------------------------------------------------------
advanced_settings() {
  # Enter alternate screen buffer to prevent flicker between dialogs
  tput smcup 2>/dev/null || true
  trap 'tput rmcup 2>/dev/null || true' RETURN

  # Initialize defaults
  TAGS="community-script;${var_tags:-}"
  local STEP=1
  local MAX_STEP=20

  # Store values for back navigation
  local _ct_type="${CT_TYPE:-1}"
  local _pw=""
  local _pw_display="Automatic Login"
  local _ct_id="$NEXTID"
  local _hostname="$NSAPP"
  local _disk_size="$var_disk"
  local _core_count="$var_cpu"
  local _ram_size="$var_ram"
  local _bridge="vmbr0"
  local _net="dhcp"
  local _gate=""
  local _ipv6_method="auto"
  local _ipv6_addr=""
  local _ipv6_gate=""
  local _apt_cacher_ip=""
  local _mtu=""
  local _sd=""
  local _ns=""
  local _mac=""
  local _vlan=""
  local _tags="$TAGS"
  local _enable_fuse="no"
  local _enable_gpu="${var_gpu:-no}"
  local _verbose="no"
  local _enable_keyctl="0"
  local _enable_mknod="0"
  local _mount_fs=""
  local _protect_ct="no"
  local _ct_timezone=""

  # Helper to show current progress
  show_progress() {
    local current=$1
    local total=$MAX_STEP
    echo -e "\n${INFO}${BOLD}${DGN}Step $current of $total${CL}"
  }

  # Detect available bridges (do this once)
  local BRIDGES=""
  local BRIDGE_MENU_OPTIONS=()
  _detect_bridges() {
    IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f 2>/dev/null)
    BRIDGES=""
    local OLD_IFS=$IFS
    IFS=$'\n'
    for iface_filepath in ${IFACE_FILEPATH_LIST}; do
      local iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX')
      (grep -Pn '^\s*iface' "${iface_filepath}" 2>/dev/null | cut -d':' -f1 && wc -l "${iface_filepath}" 2>/dev/null | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' >"${iface_indexes_tmpfile}" 2>/dev/null || true
      if [ -f "${iface_indexes_tmpfile}" ]; then
        while read -r pair; do
          local start=$(echo "${pair}" | cut -d':' -f1)
          local end=$(echo "${pair}" | cut -d':' -f2)
          if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" 2>/dev/null | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then
            local iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}')
            BRIDGES="${iface_name}"$'\n'"${BRIDGES}"
          fi
        done <"${iface_indexes_tmpfile}"
        rm -f "${iface_indexes_tmpfile}"
      fi
    done
    IFS=$OLD_IFS
    BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq)

    # Build bridge menu
    BRIDGE_MENU_OPTIONS=()
    if [[ -n "$BRIDGES" ]]; then
      while IFS= read -r bridge; do
        if [[ -n "$bridge" ]]; then
          local description=$(grep -A 10 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\s*//')
          BRIDGE_MENU_OPTIONS+=("$bridge" "${description:- }")
        fi
      done <<<"$BRIDGES"
    fi
  }
  _detect_bridges

  # Main wizard loop
  while [ $STEP -le $MAX_STEP ]; do
    case $STEP in

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 1: Container Type
    # ═══════════════════════════════════════════════════════════════════════════
    1)
      local default_on="ON"
      local default_off="OFF"
      [[ "$_ct_type" == "0" ]] && {
        default_on="OFF"
        default_off="ON"
      }

      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "CONTAINER TYPE" \
        --ok-button "Next" --cancel-button "Exit" \
        --radiolist "\nChoose container type:\n\nUse SPACE to select, ENTER to confirm." 14 58 2 \
        "1" "Unprivileged (recommended)" $default_on \
        "0" "Privileged" $default_off \
        3>&1 1>&2 2>&3); then
        [[ -n "$result" ]] && _ct_type="$result"
        ((STEP++))
      else
        exit_script
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 2: Root Password
    # ═══════════════════════════════════════════════════════════════════════════
    2)
      if PW1=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "ROOT PASSWORD" \
        --ok-button "Next" --cancel-button "Back" \
        --passwordbox "\nSet Root Password (needed for root ssh access)\n\nLeave blank for automatic login (no password)" 12 58 \
        3>&1 1>&2 2>&3); then

        if [[ -z "$PW1" ]]; then
          _pw=""
          _pw_display="Automatic Login"
          ((STEP++))
        elif [[ "$PW1" == *" "* ]]; then
          whiptail --msgbox "Password cannot contain spaces." 8 58
        elif ((${#PW1} < 5)); then
          whiptail --msgbox "Password must be at least 5 characters." 8 58
        else
          # Verify password
          if PW2=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
            --title "PASSWORD VERIFICATION" \
            --ok-button "Confirm" --cancel-button "Back" \
            --passwordbox "\nVerify Root Password" 10 58 \
            3>&1 1>&2 2>&3); then
            if [[ "$PW1" == "$PW2" ]]; then
              _pw="-password $PW1"
              _pw_display="********"
              ((STEP++))
            else
              whiptail --msgbox "Passwords do not match. Please try again." 8 58
            fi
          else
            ((STEP--))
          fi
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 3: Container ID
    # ═══════════════════════════════════════════════════════════════════════════
    3)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "CONTAINER ID" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet Container ID" 10 58 "$_ct_id" \
        3>&1 1>&2 2>&3); then
        _ct_id="${result:-$NEXTID}"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 4: Hostname
    # ═══════════════════════════════════════════════════════════════════════════
    4)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "HOSTNAME" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet Hostname (lowercase, alphanumeric, hyphens only)" 10 58 "$_hostname" \
        3>&1 1>&2 2>&3); then
        local hn_test="${result:-$NSAPP}"
        hn_test=$(echo "${hn_test,,}" | tr -d ' ')
        if [[ "$hn_test" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then
          _hostname="$hn_test"
          ((STEP++))
        else
          whiptail --msgbox "Invalid hostname: '$hn_test'\n\nOnly lowercase letters, digits and hyphens are allowed." 10 58
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 5: Disk Size
    # ═══════════════════════════════════════════════════════════════════════════
    5)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "DISK SIZE" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet Disk Size in GB" 10 58 "$_disk_size" \
        3>&1 1>&2 2>&3); then
        local disk_test="${result:-$var_disk}"
        if [[ "$disk_test" =~ ^[1-9][0-9]*$ ]]; then
          _disk_size="$disk_test"
          ((STEP++))
        else
          whiptail --msgbox "Disk size must be a positive integer!" 8 58
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 6: CPU Cores
    # ═══════════════════════════════════════════════════════════════════════════
    6)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "CPU CORES" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nAllocate CPU Cores" 10 58 "$_core_count" \
        3>&1 1>&2 2>&3); then
        local cpu_test="${result:-$var_cpu}"
        if [[ "$cpu_test" =~ ^[1-9][0-9]*$ ]]; then
          _core_count="$cpu_test"
          ((STEP++))
        else
          whiptail --msgbox "CPU core count must be a positive integer!" 8 58
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 7: RAM Size
    # ═══════════════════════════════════════════════════════════════════════════
    7)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "RAM SIZE" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nAllocate RAM in MiB" 10 58 "$_ram_size" \
        3>&1 1>&2 2>&3); then
        local ram_test="${result:-$var_ram}"
        if [[ "$ram_test" =~ ^[1-9][0-9]*$ ]]; then
          _ram_size="$ram_test"
          ((STEP++))
        else
          whiptail --msgbox "RAM size must be a positive integer!" 8 58
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 8: Network Bridge
    # ═══════════════════════════════════════════════════════════════════════════
    8)
      if [[ ${#BRIDGE_MENU_OPTIONS[@]} -eq 0 ]]; then
        _bridge="vmbr0"
        ((STEP++))
      else
        if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
          --title "NETWORK BRIDGE" \
          --ok-button "Next" --cancel-button "Back" \
          --menu "\nSelect network bridge:" 16 58 6 \
          "${BRIDGE_MENU_OPTIONS[@]}" \
          3>&1 1>&2 2>&3); then
          _bridge="${result:-vmbr0}"
          ((STEP++))
        else
          ((STEP--))
        fi
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 9: IPv4 Configuration
    # ═══════════════════════════════════════════════════════════════════════════
    9)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "IPv4 CONFIGURATION" \
        --ok-button "Next" --cancel-button "Back" \
        --menu "\nSelect IPv4 Address Assignment:" 14 60 2 \
        "dhcp" "Automatic (DHCP, recommended)" \
        "static" "Static (manual entry)" \
        3>&1 1>&2 2>&3); then

        if [[ "$result" == "static" ]]; then
          # Get static IP
          local static_ip
          if static_ip=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
            --title "STATIC IPv4 ADDRESS" \
            --ok-button "Next" --cancel-button "Back" \
            --inputbox "\nEnter Static IPv4 CIDR Address\n(e.g. 192.168.1.100/24)" 12 58 "" \
            3>&1 1>&2 2>&3); then
            if [[ "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
              # Get gateway
              local gateway_ip
              if gateway_ip=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
                --title "GATEWAY IP" \
                --ok-button "Next" --cancel-button "Back" \
                --inputbox "\nEnter Gateway IP address" 10 58 "" \
                3>&1 1>&2 2>&3); then
                if [[ "$gateway_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
                  _net="$static_ip"
                  _gate=",gw=$gateway_ip"
                  ((STEP++))
                else
                  whiptail --msgbox "Invalid Gateway IP format." 8 58
                fi
              fi
            else
              whiptail --msgbox "Invalid IPv4 CIDR format.\nExample: 192.168.1.100/24" 8 58
            fi
          fi
        else
          _net="dhcp"
          _gate=""
          ((STEP++))
        fi
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 10: IPv6 Configuration
    # ═══════════════════════════════════════════════════════════════════════════
    10)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "IPv6 CONFIGURATION" \
        --ok-button "Next" --cancel-button "Back" \
        --menu "\nSelect IPv6 Address Management:" 16 70 5 \
        "auto" "SLAAC/AUTO (recommended) - Dynamic IPv6 from network" \
        "dhcp" "DHCPv6 - DHCP-assigned IPv6 address" \
        "static" "Static - Manual IPv6 address configuration" \
        "none" "None - No IPv6 assignment (most containers)" \
        "disable" "Fully Disabled - (breaks some services)" \
        3>&1 1>&2 2>&3); then

        _ipv6_method="$result"
        case "$result" in
        static)
          local ipv6_addr
          if ipv6_addr=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
            --title "STATIC IPv6 ADDRESS" \
            --inputbox "\nEnter IPv6 CIDR address\n(e.g. 2001:db8::1/64)" 12 58 "" \
            3>&1 1>&2 2>&3); then
            if [[ "$ipv6_addr" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+(/[0-9]{1,3})$ ]]; then
              _ipv6_addr="$ipv6_addr"
              # Optional gateway
              _ipv6_gate=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
                --title "IPv6 GATEWAY" \
                --inputbox "\nEnter IPv6 gateway (optional, leave blank for none)" 10 58 "" \
                3>&1 1>&2 2>&3) || true
              ((STEP++))
            else
              whiptail --msgbox "Invalid IPv6 CIDR format." 8 58
            fi
          fi
          ;;
        dhcp)
          _ipv6_addr="dhcp"
          _ipv6_gate=""
          ((STEP++))
          ;;

        none)
          _ipv6_addr="none"
          _ipv6_gate=""
          ((STEP++))
          ;;
        *)
          _ipv6_addr=""
          _ipv6_gate=""
          ((STEP++))
          ;;
        esac
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 11: MTU Size
    # ═══════════════════════════════════════════════════════════════════════════
    11)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "MTU SIZE" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet Interface MTU Size\n(leave blank for default 1500)" 12 58 "" \
        3>&1 1>&2 2>&3); then
        _mtu="$result"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 12: DNS Search Domain
    # ═══════════════════════════════════════════════════════════════════════════
    12)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "DNS SEARCH DOMAIN" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet DNS Search Domain\n(leave blank to use host setting)" 12 58 "" \
        3>&1 1>&2 2>&3); then
        _sd="$result"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 13: DNS Server
    # ═══════════════════════════════════════════════════════════════════════════
    13)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "DNS SERVER" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet DNS Server IP\n(leave blank to use host setting)" 12 58 "" \
        3>&1 1>&2 2>&3); then
        _ns="$result"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 14: MAC Address
    # ═══════════════════════════════════════════════════════════════════════════
    14)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "MAC ADDRESS" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet MAC Address\n(leave blank for auto-generated)" 12 58 "" \
        3>&1 1>&2 2>&3); then
        _mac="$result"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 15: VLAN Tag
    # ═══════════════════════════════════════════════════════════════════════════
    15)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "VLAN TAG" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet VLAN Tag\n(leave blank for no VLAN)" 12 58 "" \
        3>&1 1>&2 2>&3); then
        _vlan="$result"
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 16: Tags
    # ═══════════════════════════════════════════════════════════════════════════
    16)
      if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "CONTAINER TAGS" \
        --ok-button "Next" --cancel-button "Back" \
        --inputbox "\nSet Custom Tags (semicolon-separated)\n(remove all for no tags)" 12 58 "$_tags" \
        3>&1 1>&2 2>&3); then
        _tags="${result:-;}"
        _tags=$(echo "$_tags" | tr -d '[:space:]')
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 17: SSH Settings
    # ═══════════════════════════════════════════════════════════════════════════
    17)
      configure_ssh_settings "Step $STEP/$MAX_STEP"
      # configure_ssh_settings handles its own flow, always advance
      ((STEP++))
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 18: FUSE & Verbose Mode
    # ═══════════════════════════════════════════════════════════════════════════
    18)
      if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "FUSE SUPPORT" \
        --ok-button "Next" --cancel-button "Back" \
        --defaultno \
        --yesno "\nEnable FUSE support?\n\nRequired for: rclone, mergerfs, AppImage, etc." 12 58; then
        _enable_fuse="yes"
      else
        if [ $? -eq 1 ]; then
          _enable_fuse="no"
        else
          ((STEP--))
          continue
        fi
      fi

      if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "VERBOSE MODE" \
        --defaultno \
        --yesno "\nEnable Verbose Mode?\n\nShows detailed output during installation." 12 58; then
        _verbose="yes"
      else
        _verbose="no"
      fi
      ((STEP++))
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 19: GPU Passthrough
    # ═══════════════════════════════════════════════════════════════════════════
    19)
      local gpu_default="OFF"
      [[ "$_enable_gpu" == "yes" ]] && gpu_default="ON"

      if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "GPU PASSTHROUGH" \
        --ok-button "Next" --cancel-button "Back" \
        --defaultno \
        --yesno "\nEnable GPU Passthrough?\n\nAutomatically detects and passes through available GPUs\n(Intel/AMD/NVIDIA) for hardware acceleration.\n\nRecommended for: Media servers, AI/ML, Transcoding" 14 62; then
        _enable_gpu="yes"
      else
        if [ $? -eq 1 ]; then
          _enable_gpu="no"
        else
          ((STEP--))
          continue
        fi
      fi
      ((STEP++))
      ;;

    # ═══════════════════════════════════════════════════════════════════════════
    # STEP 20: Confirmation
    # ═══════════════════════════════════════════════════════════════════════════
    20)
      # Build summary
      local ct_type_desc="Unprivileged"
      [[ "$_ct_type" == "0" ]] && ct_type_desc="Privileged"

      local summary="Container Type: $ct_type_desc
Container ID: $_ct_id
Hostname: $_hostname

Resources:
  Disk: ${_disk_size} GB
  CPU: $_core_count cores
  RAM: $_ram_size MiB

Network:
  Bridge: $_bridge
  IPv4: $_net
  IPv6: $_ipv6_method

Options:
  FUSE: $_enable_fuse
  GPU Passthrough: $_enable_gpu
  Verbose: $_verbose"

      if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
        --title "CONFIRM SETTINGS" \
        --ok-button "Create LXC" --cancel-button "Back" \
        --yesno "$summary\n\nCreate ${APP} LXC with these settings?" 28 58; then
        ((STEP++))
      else
        ((STEP--))
      fi
      ;;
    esac
  done

  # ═══════════════════════════════════════════════════════════════════════════
  # Apply all collected values to global variables
  # ═══════════════════════════════════════════════════════════════════════════
  CT_TYPE="$_ct_type"
  PW="$_pw"
  CT_ID="$_ct_id"
  HN="$_hostname"
  DISK_SIZE="$_disk_size"
  CORE_COUNT="$_core_count"
  RAM_SIZE="$_ram_size"
  BRG="$_bridge"
  NET="$_net"
  GATE="$_gate"
  IPV6_METHOD="$_ipv6_method"
  IPV6_ADDR="$_ipv6_addr"
  IPV6_GATE="$_ipv6_gate"
  TAGS="$_tags"
  ENABLE_FUSE="$_enable_fuse"
  ENABLE_GPU="$_enable_gpu"
  VERBOSE="$_verbose"

  # Update var_gpu based on user choice (for is_gpu_app function)
  var_gpu="$_enable_gpu"

  # Format optional values
  [[ -n "$_mtu" ]] && MTU=",mtu=$_mtu" || MTU=""
  [[ -n "$_sd" ]] && SD="-searchdomain=$_sd" || SD=""
  [[ -n "$_ns" ]] && NS="-nameserver=$_ns" || NS=""
  [[ -n "$_mac" ]] && MAC=",hwaddr=$_mac" || MAC=""
  [[ -n "$_vlan" ]] && VLAN=",tag=$_vlan" || VLAN=""

  # Alpine UDHCPC fix
  if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ -n "$_ns" ]; then
    UDHCPC_FIX="yes"
  else
    UDHCPC_FIX="no"
  fi
  export UDHCPC_FIX
  export SSH_KEYS_FILE

  # Exit alternate screen buffer before showing summary (so output remains visible)
  tput rmcup 2>/dev/null || true
  trap - RETURN

  # Display final summary
  echo -e "\n${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}"
  echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}"
  echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}"
  echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$([ "$CT_TYPE" == "1" ] && echo "Unprivileged" || echo "Privileged")${CL}"
  echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
  echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
  echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
  echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
  echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
  echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
  echo -e "${NETWORK}${BOLD}${DGN}IPv4: ${BGN}$NET${CL}"
  echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}$IPV6_METHOD${CL}"
  echo -e "${FUSE}${BOLD}${DGN}FUSE Support: ${BGN}$ENABLE_FUSE${CL}"
  echo -e "${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}$ENABLE_GPU${CL}"
  echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
  echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}"
}

# ==============================================================================
# SECTION 7: USER INTERFACE & DIAGNOSTICS
# ==============================================================================

# ------------------------------------------------------------------------------
# diagnostics_check()
#
# - Ensures diagnostics config file exists at /usr/local/community-scripts/diagnostics
# - Asks user whether to send anonymous diagnostic data
# - Saves DIAGNOSTICS=yes/no in the config file
# - Creates file if missing with default DIAGNOSTICS=yes
# - Reads current diagnostics setting from file
# - Sets global DIAGNOSTICS variable for API telemetry opt-in/out
# ------------------------------------------------------------------------------
diagnostics_check() {
  if ! [ -d "/usr/local/community-scripts" ]; then
    mkdir -p /usr/local/community-scripts
  fi

  if ! [ -f "/usr/local/community-scripts/diagnostics" ]; then
    if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "DIAGNOSTICS" --yesno "Send Diagnostics of LXC Installation?\n\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)" 10 58); then
      cat <<EOF >/usr/local/community-scripts/diagnostics
DIAGNOSTICS=yes

#This file is used to store the diagnostics settings for the Community-Scripts API.
#https://github.com/community-scripts/ProxmoxVE/discussions/1836
#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.
#You can review the data at https://community-scripts.github.io/ProxmoxVE/data
#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue.
#This will disable the diagnostics feature.
#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue.
#This will enable the diagnostics feature.
#The following information will be sent:
#"disk_size"
#"core_count"
#"ram_size"
#"os_type"
#"os_version"
#"nsapp"
#"method"
#"pve_version"
#"status"
#If you have any concerns, please review the source code at /misc/build.func
EOF
      DIAGNOSTICS="yes"
    else
      cat <<EOF >/usr/local/community-scripts/diagnostics
DIAGNOSTICS=no

#This file is used to store the diagnostics settings for the Community-Scripts API.
#https://github.com/community-scripts/ProxmoxVE/discussions/1836
#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.
#You can review the data at https://community-scripts.github.io/ProxmoxVE/data
#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue.
#This will disable the diagnostics feature.
#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue.
#This will enable the diagnostics feature.
#The following information will be sent:
#"disk_size"
#"core_count"
#"ram_size"
#"os_type"
#"os_version"
#"nsapp"
#"method"
#"pve_version"
#"status"
#If you have any concerns, please review the source code at /misc/build.func
EOF
      DIAGNOSTICS="no"
    fi
  else
    DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics)

  fi
}

diagnostics_menu() {
  if [ "${DIAGNOSTICS:-no}" = "yes" ]; then
    if whiptail --backtitle "Proxmox VE Helper Scripts" \
      --title "DIAGNOSTIC SETTINGS" \
      --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \
      --yes-button "No" --no-button "Back"; then
      DIAGNOSTICS="no"
      sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics
      whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58
    fi
  else
    if whiptail --backtitle "Proxmox VE Helper Scripts" \
      --title "DIAGNOSTIC SETTINGS" \
      --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \
      --yes-button "Yes" --no-button "Back"; then
      DIAGNOSTICS="yes"
      sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics
      whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58
    fi
  fi
}

# ------------------------------------------------------------------------------
# echo_default()
#
# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.)
# - Uses icons and formatting for readability
# - Convert CT_TYPE to description
# ------------------------------------------------------------------------------
echo_default() {
  CT_TYPE_DESC="Unprivileged"
  if [ "$CT_TYPE" -eq 0 ]; then
    CT_TYPE_DESC="Privileged"
  fi
  echo -e "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}"
  echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}"
  echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}"
  echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
  echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
  echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
  echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
  if [[ -n "${var_gpu:-}" && "${var_gpu}" == "yes" ]]; then
    echo -e "${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}Enabled${CL}"
  fi
  if [ "$VERBOSE" == "yes" ]; then
    echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}"
  fi
  echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}"
  echo -e "  "
}

# ------------------------------------------------------------------------------
# install_script()
#
# - Main entrypoint for installation mode
# - Runs safety checks (pve_check, root_check, maxkeys_check, diagnostics_check)
# - Builds interactive menu (Default, Verbose, Advanced, My Defaults, App Defaults, Diagnostics, Storage, Exit)
# - Applies chosen settings and triggers container build
# ------------------------------------------------------------------------------
install_script() {
  pve_check
  shell_check
  root_check
  arch_check
  ssh_check
  maxkeys_check
  diagnostics_check

  if systemctl is-active -q ping-instances.service; then
    systemctl -q stop ping-instances.service
  fi

  NEXTID=$(pvesh get /cluster/nextid)

  # Get timezone using timedatectl (Debian 13+ compatible)
  # Fallback to /etc/timezone for older systems
  if command -v timedatectl >/dev/null 2>&1; then
    timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC")
  elif [ -f /etc/timezone ]; then
    timezone=$(cat /etc/timezone)
  else
    timezone="UTC"
  fi

  # Show APP Header
  header_info

  # --- Support CLI argument as direct preset (default, advanced, …) ---
  CHOICE="${mode:-${1:-}}"

  # If no CLI argument → show whiptail menu
  # Build menu dynamically based on available options
  local appdefaults_option=""
  local settings_option=""
  local menu_items=(
    "1" "Default Install"
    "2" "Advanced Install"
    "3" "User Defaults"
  )

  if [ -f "$(get_app_defaults_path)" ]; then
    appdefaults_option="4"
    menu_items+=("4" "App Defaults for ${APP}")
    settings_option="5"
    menu_items+=("5" "Settings")
  else
    settings_option="4"
    menu_items+=("4" "Settings")
  fi

  APPDEFAULTS_OPTION="$appdefaults_option"
  SETTINGS_OPTION="$settings_option"

  # Main menu loop - allows returning from Settings
  while true; do
    if [ -z "$CHOICE" ]; then
      TMP_CHOICE=$(whiptail \
        --backtitle "Proxmox VE Helper Scripts" \
        --title "Community-Scripts Options" \
        --ok-button "Select" --cancel-button "Exit Script" \
        --notags \
        --menu "\nChoose an option:\n Use TAB or Arrow keys to navigate, ENTER to select.\n" \
        20 60 9 \
        "${menu_items[@]}" \
        --default-item "1" \
        3>&1 1>&2 2>&3) || exit_script
      CHOICE="$TMP_CHOICE"
    fi

    # --- Main case ---
    local defaults_target=""
    local run_maybe_offer="no"
    case "$CHOICE" in
    1 | default | DEFAULT)
      header_info
      echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}"
      VERBOSE="no"
      METHOD="default"
      base_settings "$VERBOSE"
      echo_default
      defaults_target="$(ensure_global_default_vars_file)"
      break
      ;;
    2 | advanced | ADVANCED)
      header_info
      echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}"
      echo -e "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}"
      METHOD="advanced"
      base_settings
      advanced_settings
      defaults_target="$(ensure_global_default_vars_file)"
      run_maybe_offer="yes"
      break
      ;;
    3 | mydefaults | MYDEFAULTS | userdefaults | USERDEFAULTS)
      default_var_settings || {
        msg_error "Failed to apply default.vars"
        exit 1
      }
      defaults_target="/usr/local/community-scripts/default.vars"
      break
      ;;
    "$APPDEFAULTS_OPTION" | appdefaults | APPDEFAULTS)
      if [ -f "$(get_app_defaults_path)" ]; then
        header_info
        echo -e "${DEFAULT}${BOLD}${BL}Using App Defaults for ${APP} on node $PVEHOST_NAME${CL}"
        METHOD="appdefaults"
        base_settings
        load_vars_file "$(get_app_defaults_path)"
        echo_default
        defaults_target="$(get_app_defaults_path)"
        break
      else
        msg_error "No App Defaults available for ${APP}"
        exit 1
      fi
      ;;
    "$SETTINGS_OPTION" | settings | SETTINGS)
      settings_menu
      # After settings menu, show main menu again
      header_info
      CHOICE=""
      ;;
    *)
      echo -e "${CROSS}${RD}Invalid option: $CHOICE${CL}"
      exit 1
      ;;
    esac
  done

  if [[ -n "$defaults_target" ]]; then
    ensure_storage_selection_for_vars_file "$defaults_target"
  fi

  if [[ "$run_maybe_offer" == "yes" ]]; then
    maybe_offer_save_app_defaults
  fi
}

edit_default_storage() {
  local vf="/usr/local/community-scripts/default.vars"

  # Ensure file exists
  if [[ ! -f "$vf" ]]; then
    mkdir -p "$(dirname "$vf")"
    touch "$vf"
  fi

  # Let ensure_storage_selection_for_vars_file handle everything
  ensure_storage_selection_for_vars_file "$vf"
}

settings_menu() {
  while true; do
    local settings_items=(
      "1" "Manage API-Diagnostic Setting"
      "2" "Edit Default.vars"
    )
    if [ -f "$(get_app_defaults_path)" ]; then
      settings_items+=("3" "Edit App.vars for ${APP}")
      settings_items+=("4" "Back to Main Menu")
    else
      settings_items+=("3" "Back to Main Menu")
    fi

    local choice
    choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
      --title "Community-Scripts SETTINGS Menu" \
      --ok-button "Select" --cancel-button "Exit Script" \
      --menu "\n\nChoose a settings option:\n\nUse Arrow keys to navigate, ENTER to select, TAB for buttons." 20 60 9 \
      "${settings_items[@]}" \
      3>&1 1>&2 2>&3) || exit_script

    case "$choice" in
    1) diagnostics_menu ;;
    2) nano /usr/local/community-scripts/default.vars ;;
    3)
      if [ -f "$(get_app_defaults_path)" ]; then
        nano "$(get_app_defaults_path)"
      else
        # Back was selected (no app.vars available)
        return
      fi
      ;;
    4)
      # Back to main menu
      return
      ;;
    esac
  done
}

# ------------------------------------------------------------------------------
# check_container_resources()
#
# - Compares host RAM/CPU with required values
# - Warns if under-provisioned and asks user to continue or abort
# ------------------------------------------------------------------------------
check_container_resources() {
  current_ram=$(free -m | awk 'NR==2{print $2}')
  current_cpu=$(nproc)

  if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then
    echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}"
    echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n"
    echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No>  "
    read -r prompt
    if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
      echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}"
      exit 1
    fi
  else
    echo -e ""
  fi
}

# ------------------------------------------------------------------------------
# check_container_storage()
#
# - Checks /boot partition usage
# - Warns if usage >80% and asks user confirmation before proceeding
# ------------------------------------------------------------------------------
check_container_storage() {
  total_size=$(df /boot --output=size | tail -n 1)
  local used_size=$(df /boot --output=used | tail -n 1)
  usage=$((100 * used_size / total_size))
  if ((usage > 80)); then
    echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}"
    echo -ne "Continue anyway? <y/N>  "
    read -r prompt
    if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
      echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}"
      exit 1
    fi
  fi
}

# ------------------------------------------------------------------------------
# ssh_extract_keys_from_file()
#
# - Extracts valid SSH public keys from given file
# - Supports RSA, Ed25519, ECDSA and filters out comments/invalid lines
# ------------------------------------------------------------------------------
ssh_extract_keys_from_file() {
  local f="$1"
  [[ -r "$f" ]] || return 0
  tr -d '\r' <"$f" | awk '
    /^[[:space:]]*#/ {next}
    /^[[:space:]]*$/ {next}
    # bare format: type base64 [comment]
    /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/ {print; next}
    # with options: find from first key-type onward
    {
      match($0, /(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/)
      if (RSTART>0) { print substr($0, RSTART) }
    }
  '
}

# ------------------------------------------------------------------------------
# ssh_build_choices_from_files()
#
# - Builds interactive whiptail checklist of available SSH keys
# - Generates fingerprint, type and comment for each key
# ------------------------------------------------------------------------------
ssh_build_choices_from_files() {
  local -a files=("$@")
  CHOICES=()
  COUNT=0
  MAPFILE="$(mktemp)"
  local id key typ fp cmt base ln=0

  for f in "${files[@]}"; do
    [[ -f "$f" && -r "$f" ]] || continue
    base="$(basename -- "$f")"
    case "$base" in
    known_hosts | known_hosts.* | config) continue ;;
    id_*) [[ "$f" != *.pub ]] && continue ;;
    esac

    # map every key in file
    while IFS= read -r key; do
      [[ -n "$key" ]] || continue

      typ=""
      fp=""
      cmt=""
      # Only the pure key part (without options) is already included in ‘key’.
      read -r _typ _b64 _cmt <<<"$key"
      typ="${_typ:-key}"
      cmt="${_cmt:-}"
      # Fingerprint via ssh-keygen (if available)
      if command -v ssh-keygen >/dev/null 2>&1; then
        fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')"
      fi
      # Label shorten
      [[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..."

      ln=$((ln + 1))
      COUNT=$((COUNT + 1))
      id="K${COUNT}"
      echo "${id}|${key}" >>"$MAPFILE"
      CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}" "OFF")
    done < <(ssh_extract_keys_from_file "$f")
  done
}

# ------------------------------------------------------------------------------
# ssh_discover_default_files()
#
# - Scans standard paths for SSH keys
# - Includes ~/.ssh/*.pub, /etc/ssh/authorized_keys, etc.
# ------------------------------------------------------------------------------
ssh_discover_default_files() {
  local -a cand=()
  shopt -s nullglob
  cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)
  cand+=(/root/.ssh/*.pub)
  cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)
  shopt -u nullglob
  printf '%s\0' "${cand[@]}"
}

configure_ssh_settings() {
  local step_info="${1:-}"
  local backtitle="Proxmox VE Helper Scripts"
  [[ -n "$step_info" ]] && backtitle="Proxmox VE Helper Scripts [${step_info}]"

  SSH_KEYS_FILE="$(mktemp)"
  : >"$SSH_KEYS_FILE"

  IFS=$'\0' read -r -d '' -a _def_files < <(ssh_discover_default_files && printf '\0')
  ssh_build_choices_from_files "${_def_files[@]}"
  local default_key_count="$COUNT"

  local ssh_key_mode
  if [[ "$default_key_count" -gt 0 ]]; then
    ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \
      "Provision SSH keys for root:" 14 72 4 \
      "found" "Select from detected keys (${default_key_count})" \
      "manual" "Paste a single public key" \
      "folder" "Scan another folder (path or glob)" \
      "none" "No keys" 3>&1 1>&2 2>&3) || exit_script
  else
    ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \
      "No host keys detected; choose manual/none:" 12 72 2 \
      "manual" "Paste a single public key" \
      "none" "No keys" 3>&1 1>&2 2>&3) || exit_script
  fi

  case "$ssh_key_mode" in
  found)
    local selection
    selection=$(whiptail --backtitle "$backtitle" --title "SELECT HOST KEYS" \
      --checklist "Select one or more keys to import:" 20 140 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script
    for tag in $selection; do
      tag="${tag%\"}"
      tag="${tag#\"}"
      local line
      line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-)
      [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
    done
    ;;
  manual)
    SSH_AUTHORIZED_KEY="$(whiptail --backtitle "$backtitle" \
      --inputbox "Paste one SSH public key line (ssh-ed25519/ssh-rsa/...)" 10 72 --title "SSH Public Key" 3>&1 1>&2 2>&3)"
    [[ -n "$SSH_AUTHORIZED_KEY" ]] && printf '%s\n' "$SSH_AUTHORIZED_KEY" >>"$SSH_KEYS_FILE"
    ;;
  folder)
    local glob_path
    glob_path=$(whiptail --backtitle "$backtitle" \
      --inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub)" 10 72 --title "Scan Folder/Glob" 3>&1 1>&2 2>&3)
    if [[ -n "$glob_path" ]]; then
      shopt -s nullglob
      read -r -a _scan_files <<<"$glob_path"
      shopt -u nullglob
      if [[ "${#_scan_files[@]}" -gt 0 ]]; then
        ssh_build_choices_from_files "${_scan_files[@]}"
        if [[ "$COUNT" -gt 0 ]]; then
          local folder_selection
          folder_selection=$(whiptail --backtitle "$backtitle" --title "SELECT FOLDER KEYS" \
            --checklist "Select key(s) to import:" 20 78 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script
          for tag in $folder_selection; do
            tag="${tag%\"}"
            tag="${tag#\"}"
            local line
            line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-)
            [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
          done
        else
          whiptail --backtitle "$backtitle" --msgbox "No keys found in: $glob_path" 8 60
        fi
      else
        whiptail --backtitle "$backtitle" --msgbox "Path/glob returned no files." 8 60
      fi
    fi
    ;;
  none)
    :
    ;;
  esac

  if [[ -s "$SSH_KEYS_FILE" ]]; then
    sort -u -o "$SSH_KEYS_FILE" "$SSH_KEYS_FILE"
    printf '\n' >>"$SSH_KEYS_FILE"
  fi

  # Always show SSH access dialog - user should be able to enable SSH even without keys
  if (whiptail --backtitle "$backtitle" --defaultno --title "SSH ACCESS" --yesno "Enable root SSH access?" 10 58); then
    SSH="yes"
  else
    SSH="no"
  fi
}

# ------------------------------------------------------------------------------
# start()
#
# - Entry point of script
# - On Proxmox host: calls install_script
# - In silent mode: runs update_script
# - Otherwise: shows update/setting menu
# ------------------------------------------------------------------------------
start() {
  source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
  if command -v pveversion >/dev/null 2>&1; then
    install_script || return 0
    return 0
  elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then
    VERBOSE="no"
    set_std_mode
    update_script
  else
    CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \
      "Support/Update functions for ${APP} LXC. Choose an option:" \
      12 60 3 \
      "1" "YES (Silent Mode)" \
      "2" "YES (Verbose Mode)" \
      "3" "NO (Cancel Update)" --nocancel --default-item "1" 3>&1 1>&2 2>&3)

    case "$CHOICE" in
    1)
      VERBOSE="no"
      set_std_mode
      ;;
    2)
      VERBOSE="yes"
      set_std_mode
      ;;
    3)
      clear
      exit_script
      exit
      ;;
    esac
    update_script
  fi
}

# ==============================================================================
# SECTION 8: CONTAINER CREATION & DEPLOYMENT
# ==============================================================================

# ------------------------------------------------------------------------------
# build_container()
#
# - Main function for creating and configuring LXC container
# - Builds network configuration string (IP, gateway, VLAN, MTU, MAC, IPv6)
# - Creates container via pct create with all specified settings
# - Applies features: FUSE, TUN, keyctl, VAAPI passthrough
# - Starts container and waits for network connectivity
# - Installs base packages (curl, sudo, etc.)
# - Injects SSH keys if configured
# - Executes <app>-install.sh inside container
# - Posts installation telemetry to API if diagnostics enabled
# ------------------------------------------------------------------------------
build_container() {
  #  if [ "$VERBOSE" == "yes" ]; then set -x; fi

  NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}"

  # MAC
  if [[ -n "$MAC" ]]; then
    case "$MAC" in
    ,hwaddr=*) NET_STRING+="$MAC" ;;
    *) NET_STRING+=",hwaddr=$MAC" ;;
    esac
  fi

  # IP (always required, default dhcp)
  NET_STRING+=",ip=${NET:-dhcp}"

  # Gateway
  if [[ -n "$GATE" ]]; then
    case "$GATE" in
    ,gw=*) NET_STRING+="$GATE" ;;
    *) NET_STRING+=",gw=$GATE" ;;
    esac
  fi

  # VLAN
  if [[ -n "$VLAN" ]]; then
    case "$VLAN" in
    ,tag=*) NET_STRING+="$VLAN" ;;
    *) NET_STRING+=",tag=$VLAN" ;;
    esac
  fi

  # MTU
  if [[ -n "$MTU" ]]; then
    case "$MTU" in
    ,mtu=*) NET_STRING+="$MTU" ;;
    *) NET_STRING+=",mtu=$MTU" ;;
    esac
  fi

  # IPv6 Handling
  case "$IPV6_METHOD" in
  auto) NET_STRING="$NET_STRING,ip6=auto" ;;
  dhcp) NET_STRING="$NET_STRING,ip6=dhcp" ;;
  static)
    NET_STRING="$NET_STRING,ip6=$IPV6_ADDR"
    [ -n "$IPV6_GATE" ] && NET_STRING="$NET_STRING,gw6=$IPV6_GATE"
    ;;
  none) ;;
  esac

  # Build FEATURES string
  if [ "$CT_TYPE" == "1" ]; then
    FEATURES="keyctl=1,nesting=1"
  else
    FEATURES="nesting=1"
  fi

  if [ "$ENABLE_FUSE" == "yes" ]; then
    FEATURES="$FEATURES,fuse=1"
  fi

  # Build PCT_OPTIONS as string for export
  TEMP_DIR=$(mktemp -d)
  pushd "$TEMP_DIR" >/dev/null
  if [ "$var_os" == "alpine" ]; then
    export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func)"
  else
    export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)"
  fi

  # Core exports for install.func
  export DIAGNOSTICS="$DIAGNOSTICS"
  export RANDOM_UUID="$RANDOM_UUID"
  export SESSION_ID="$SESSION_ID"
  export CACHER="$APT_CACHER"
  export CACHER_IP="$APT_CACHER_IP"
  export tz="$timezone"
  export APPLICATION="$APP"
  export app="$NSAPP"
  export PASSWORD="$PW"
  export VERBOSE="$VERBOSE"
  export SSH_ROOT="${SSH}"
  export SSH_AUTHORIZED_KEY
  export CTID="$CT_ID"
  export CTTYPE="$CT_TYPE"
  export ENABLE_FUSE="$ENABLE_FUSE"
  export ENABLE_TUN="$ENABLE_TUN"
  export PCT_OSTYPE="$var_os"
  export PCT_OSVERSION="$var_version"
  export PCT_DISK_SIZE="$DISK_SIZE"

  # DEV_MODE exports (optional, for debugging)
  export BUILD_LOG="$BUILD_LOG"
  export INSTALL_LOG="/root/.install-${SESSION_ID}.log"
  export dev_mode="${dev_mode:-}"
  export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}"
  export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}"
  export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}"
  export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}"
  export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}"
  export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}"
  export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}"

  # Build PCT_OPTIONS as multi-line string
  PCT_OPTIONS_STRING="  -features $FEATURES
  -hostname $HN
  -tags $TAGS"

  # Add storage if specified
  if [ -n "$SD" ]; then
    PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  $SD"
  fi

  # Add nameserver if specified
  if [ -n "$NS" ]; then
    PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  $NS"
  fi

  # Network configuration
  PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  $NET_STRING
  -onboot 1
  -cores $CORE_COUNT
  -memory $RAM_SIZE
  -unprivileged $CT_TYPE"

  # Protection flag (if var_protection was set)
  if [ "${PROTECT_CT:-}" == "1" ] || [ "${PROTECT_CT:-}" == "yes" ]; then
    PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  -protection 1"
  fi

  # Timezone flag (if var_timezone was set)
  if [ -n "${CT_TIMEZONE:-}" ]; then
    PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  -timezone $CT_TIMEZONE"
  fi

  # Password (already formatted)
  if [ -n "$PW" ]; then
    PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
  $PW"
  fi

  # Export as string (this works, unlike arrays!)
  export PCT_OPTIONS="$PCT_OPTIONS_STRING"
  export TEMPLATE_STORAGE="${var_template_storage:-}"
  export CONTAINER_STORAGE="${var_container_storage:-}"

  create_lxc_container || exit $?

  LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"

  # ============================================================================
  # GPU/USB PASSTHROUGH CONFIGURATION
  # ============================================================================

  # Check if GPU passthrough is enabled
  # Returns true only if var_gpu is explicitly set to "yes"
  # Can be set via:
  #   - Environment variable: var_gpu=yes bash -c "..."
  #   - CT script default: var_gpu="${var_gpu:-no}"
  #   - Advanced settings wizard
  #   - App defaults file: /usr/local/community-scripts/defaults/<app>.vars
  is_gpu_app() {
    [[ "${var_gpu:-no}" == "yes" ]] && return 0
    return 1
  }

  # Detect all available GPU devices
  detect_gpu_devices() {
    INTEL_DEVICES=()
    AMD_DEVICES=()
    NVIDIA_DEVICES=()

    # Store PCI info to avoid multiple calls
    local pci_vga_info=$(lspci -nn 2>/dev/null | grep -E "VGA|Display|3D")

    # Check for Intel GPU - look for Intel vendor ID [8086]
    if echo "$pci_vga_info" | grep -q "\[8086:"; then
      msg_custom "🎮" "${BL}" "Detected Intel GPU"
      if [[ -d /dev/dri ]]; then
        for d in /dev/dri/renderD* /dev/dri/card*; do
          [[ -e "$d" ]] && INTEL_DEVICES+=("$d")
        done
      fi
    fi

    # Check for AMD GPU - look for AMD vendor IDs [1002] (AMD/ATI) or [1022] (AMD)
    if echo "$pci_vga_info" | grep -qE "\[1002:|\[1022:"; then
      msg_custom "🎮" "${RD}" "Detected AMD GPU"
      if [[ -d /dev/dri ]]; then
        # Only add if not already claimed by Intel
        if [[ ${#INTEL_DEVICES[@]} -eq 0 ]]; then
          for d in /dev/dri/renderD* /dev/dri/card*; do
            [[ -e "$d" ]] && AMD_DEVICES+=("$d")
          done
        fi
      fi
    fi

    # Check for NVIDIA GPU - look for NVIDIA vendor ID [10de]
    if echo "$pci_vga_info" | grep -q "\[10de:"; then
      msg_custom "🎮" "${GN}" "Detected NVIDIA GPU"

      # Simple passthrough - just bind /dev/nvidia* devices if they exist
      # Only include character devices (-c), skip directories like /dev/nvidia-caps
      for d in /dev/nvidia*; do
        [[ -c "$d" ]] && NVIDIA_DEVICES+=("$d")
      done
      # Also check for devices inside /dev/nvidia-caps/ directory
      if [[ -d /dev/nvidia-caps ]]; then
        for d in /dev/nvidia-caps/*; do
          [[ -c "$d" ]] && NVIDIA_DEVICES+=("$d")
        done
      fi

      if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then
        msg_custom "🎮" "${GN}" "Found ${#NVIDIA_DEVICES[@]} NVIDIA device(s) for passthrough"
      else
        msg_warn "NVIDIA GPU detected via PCI but no /dev/nvidia* devices found"
        msg_custom "ℹ️" "${YW}" "Skipping NVIDIA passthrough (host drivers may not be loaded)"
      fi
    fi

    # Debug output
    msg_debug "Intel devices: ${INTEL_DEVICES[*]}"
    msg_debug "AMD devices: ${AMD_DEVICES[*]}"
    msg_debug "NVIDIA devices: ${NVIDIA_DEVICES[*]}"
  }

  # Configure USB passthrough for privileged containers
  configure_usb_passthrough() {
    if [[ "$CT_TYPE" != "0" ]]; then
      return 0
    fi

    msg_info "Configuring automatic USB passthrough (privileged container)"
    cat <<EOF >>"$LXC_CONFIG"
# Automatic USB passthrough (privileged container)
lxc.cgroup2.devices.allow: a
lxc.cap.drop:
lxc.cgroup2.devices.allow: c 188:* rwm
lxc.cgroup2.devices.allow: c 189:* rwm
lxc.mount.entry: /dev/serial/by-id  dev/serial/by-id  none bind,optional,create=dir
lxc.mount.entry: /dev/ttyUSB0       dev/ttyUSB0       none bind,optional,create=file
lxc.mount.entry: /dev/ttyUSB1       dev/ttyUSB1       none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM0       dev/ttyACM0       none bind,optional,create=file
lxc.mount.entry: /dev/ttyACM1       dev/ttyACM1       none bind,optional,create=file
EOF
    msg_ok "USB passthrough configured"
  }

  # Configure GPU passthrough
  configure_gpu_passthrough() {
    # Skip if:
    # GPU passthrough is enabled when var_gpu="yes":
    # - Set via environment variable: var_gpu=yes bash -c "..."
    # - Set in CT script: var_gpu="${var_gpu:-no}"
    # - Enabled in advanced_settings wizard
    # - Configured in app defaults file
    if ! is_gpu_app "$APP"; then
      return 0
    fi

    detect_gpu_devices

    # Count available GPU types
    local gpu_count=0
    local available_gpus=()

    if [[ ${#INTEL_DEVICES[@]} -gt 0 ]]; then
      available_gpus+=("INTEL")
      gpu_count=$((gpu_count + 1))
    fi

    if [[ ${#AMD_DEVICES[@]} -gt 0 ]]; then
      available_gpus+=("AMD")
      gpu_count=$((gpu_count + 1))
    fi

    if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then
      available_gpus+=("NVIDIA")
      gpu_count=$((gpu_count + 1))
    fi

    if [[ $gpu_count -eq 0 ]]; then
      msg_custom "ℹ️" "${YW}" "No GPU devices found for passthrough"
      return 0
    fi

    local selected_gpu=""

    if [[ $gpu_count -eq 1 ]]; then
      # Automatic selection for single GPU
      selected_gpu="${available_gpus[0]}"
      msg_ok "Automatically configuring ${selected_gpu} GPU passthrough"
    else
      # Multiple GPUs - ask user
      echo -e "\n${INFO} Multiple GPU types detected:"
      for gpu in "${available_gpus[@]}"; do
        echo "  - $gpu"
      done
      read -rp "Which GPU type to passthrough? (${available_gpus[*]}): " selected_gpu
      selected_gpu="${selected_gpu^^}"

      # Validate selection
      local valid=0
      for gpu in "${available_gpus[@]}"; do
        [[ "$selected_gpu" == "$gpu" ]] && valid=1
      done

      if [[ $valid -eq 0 ]]; then
        msg_warn "Invalid selection. Skipping GPU passthrough."
        return 0
      fi
    fi

    # Apply passthrough configuration based on selection
    local dev_idx=0

    case "$selected_gpu" in
    INTEL | AMD)
      local devices=()
      [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}")
      [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}")

      # Use pct set to add devices with proper dev0/dev1 format
      # GIDs will be detected and set after container starts
      local dev_index=0
      for dev in "${devices[@]}"; do
        # Add to config using pct set (will be visible in GUI)
        echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG"
        dev_index=$((dev_index + 1))
      done

      export GPU_TYPE="$selected_gpu"
      msg_ok "${selected_gpu} GPU passthrough configured (${#devices[@]} devices)"
      ;;

    NVIDIA)
      if [[ ${#NVIDIA_DEVICES[@]} -eq 0 ]]; then
        msg_warn "No NVIDIA devices available for passthrough"
        return 0
      fi

      # Use pct set for NVIDIA devices
      local dev_index=0
      for dev in "${NVIDIA_DEVICES[@]}"; do
        echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG"
        dev_index=$((dev_index + 1))
      done

      export GPU_TYPE="NVIDIA"
      msg_ok "NVIDIA GPU passthrough configured (${#NVIDIA_DEVICES[@]} devices) - install drivers in container if needed"
      ;;
    esac
  }

  # Additional device passthrough
  configure_additional_devices() {
    # TUN device passthrough
    if [ "$ENABLE_TUN" == "yes" ]; then
      cat <<EOF >>"$LXC_CONFIG"
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
EOF
    fi

    # Coral TPU passthrough
    if [[ -e /dev/apex_0 ]]; then
      msg_custom "🔌" "${BL}" "Detected Coral TPU - configuring passthrough"
      echo "lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file" >>"$LXC_CONFIG"
    fi
  }

  # Execute pre-start configurations
  configure_usb_passthrough
  configure_gpu_passthrough
  configure_additional_devices

  # ============================================================================
  # START CONTAINER AND INSTALL USERLAND
  # ============================================================================

  msg_info "Starting LXC Container"
  pct start "$CTID"

  # Wait for container to be running
  for i in {1..10}; do
    if pct status "$CTID" | grep -q "status: running"; then
      msg_ok "Started LXC Container"
      break
    fi
    sleep 1
    if [ "$i" -eq 10 ]; then
      msg_error "LXC Container did not reach running state"
      exit 1
    fi
  done

  # Wait for network (skip for Alpine initially)
  if [ "$var_os" != "alpine" ]; then
    msg_info "Waiting for network in LXC container"

    # Wait for IP assignment (IPv4 or IPv6)
    local ip_in_lxc=""
    for i in {1..20}; do
      # Try IPv4 first
      ip_in_lxc=$(pct exec "$CTID" -- ip -4 addr show dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
      # Fallback to IPv6 if IPv4 not available
      if [ -z "$ip_in_lxc" ]; then
        ip_in_lxc=$(pct exec "$CTID" -- ip -6 addr show dev eth0 scope global 2>/dev/null | awk '/inet6 / {print $2}' | cut -d/ -f1 | head -n1)
      fi
      [ -n "$ip_in_lxc" ] && break
      sleep 1
    done

    if [ -z "$ip_in_lxc" ]; then
      msg_error "No IP assigned to CT $CTID after 20s"
      echo -e "${YW}Troubleshooting:${CL}"
      echo "  • Verify bridge ${BRG} exists and has connectivity"
      echo "  • Check if DHCP server is reachable (if using DHCP)"
      echo "  • Verify static IP configuration (if using static IP)"
      echo "  • Check Proxmox firewall rules"
      echo "  • If using Tailscale: Disable MagicDNS temporarily"
      exit 1
    fi

    # Verify basic connectivity (ping test)
    local ping_success=false
    for retry in {1..3}; do
      if pct exec "$CTID" -- ping -c 1 -W 2 1.1.1.1 &>/dev/null ||
        pct exec "$CTID" -- ping -c 1 -W 2 8.8.8.8 &>/dev/null ||
        pct exec "$CTID" -- ping6 -c 1 -W 2 2606:4700:4700::1111 &>/dev/null; then
        ping_success=true
        break
      fi
      sleep 2
    done

    if [ "$ping_success" = false ]; then
      msg_warn "Network configured (IP: $ip_in_lxc) but connectivity test failed"
      echo -e "${YW}Container may have limited internet access. Installation will continue...${CL}"
    else
      msg_ok "Network in LXC is reachable (ping)"
    fi
  fi
  # Function to get correct GID inside container
  get_container_gid() {
    local group="$1"
    local gid=$(pct exec "$CTID" -- getent group "$group" 2>/dev/null | cut -d: -f3)
    echo "${gid:-44}" # Default to 44 if not found
  }

  fix_gpu_gids

  # Continue with standard container setup
  msg_info "Customizing LXC Container"

  # # Install GPU userland if configured
  # if [[ "${ENABLE_VAAPI:-0}" == "1" ]]; then
  #   install_gpu_userland "VAAPI"
  # fi

  # if [[ "${ENABLE_NVIDIA:-0}" == "1" ]]; then
  #   install_gpu_userland "NVIDIA"
  # fi

  # Continue with standard container setup
  if [ "$var_os" == "alpine" ]; then
    sleep 3
    pct exec "$CTID" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories
http://dl-cdn.alpinelinux.org/alpine/latest-stable/main
http://dl-cdn.alpinelinux.org/alpine/latest-stable/community
EOF'
    pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null"
  else
    sleep 3
    pct exec "$CTID" -- bash -c "sed -i '/$LANG/ s/^# //' /etc/locale.gen"
    pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \
    echo LANG=\$locale_line >/etc/default/locale && \
    locale-gen >/dev/null && \
    export LANG=\$locale_line"

    if [[ -z "${tz:-}" ]]; then
      tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Etc/UTC")
    fi

    if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then
      # Set timezone using symlink (Debian 13+ compatible)
      # Create /etc/timezone for backwards compatibility with older scripts
      pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true"
    else
      msg_warn "Skipping timezone setup – zone '$tz' not found in container"
    fi

    pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 jq >/dev/null" || {
      msg_error "apt-get base packages installation failed"
      exit 1
    }
  fi

  msg_ok "Customized LXC Container"

  # Install SSH keys
  install_ssh_keys_into_ct

  # Run application installer
  # Disable error trap - container errors are handled internally via flag file
  set +Eeuo pipefail # Disable ALL error handling temporarily
  trap - ERR         # Remove ERR trap completely

  lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)"
  local lxc_exit=$?

  set -Eeuo pipefail       # Re-enable error handling
  trap 'error_handler' ERR # Restore ERR trap

  # Check for error flag file in container (more reliable than lxc-attach exit code)
  local install_exit_code=0
  if [[ -n "${SESSION_ID:-}" ]]; then
    local error_flag="/root/.install-${SESSION_ID}.failed"
    if pct exec "$CTID" -- test -f "$error_flag" 2>/dev/null; then
      install_exit_code=$(pct exec "$CTID" -- cat "$error_flag" 2>/dev/null || echo "1")
      pct exec "$CTID" -- rm -f "$error_flag" 2>/dev/null || true
    fi
  fi

  # Fallback to lxc-attach exit code if no flag file
  if [[ $install_exit_code -eq 0 && $lxc_exit -ne 0 ]]; then
    install_exit_code=$lxc_exit
  fi

  # Installation failed?
  if [[ $install_exit_code -ne 0 ]]; then
    msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})"

    # Copy both logs from container before potential deletion
    local build_log_copied=false
    local install_log_copied=false

    if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
      # Copy BUILD_LOG (creation log) if it exists
      if [[ -f "${BUILD_LOG}" ]]; then
        cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true
      fi

      # Copy INSTALL_LOG from container
      if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then
        install_log_copied=true
      fi

      # Show available logs
      echo ""
      [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}"
      [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}"
    fi

    # Dev mode: Keep container or open breakpoint shell
    if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then
      msg_dev "Keep mode active - container ${CTID} preserved"
      return 0
    elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then
      msg_dev "Breakpoint mode - opening shell in container ${CTID}"
      echo -e "${YW}Type 'exit' to return to host${CL}"
      pct enter "$CTID"
      echo ""
      echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}"
      if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then
        pct stop "$CTID" &>/dev/null || true
        pct destroy "$CTID" &>/dev/null || true
        msg_ok "Container ${CTID} removed"
      else
        msg_dev "Container ${CTID} kept for debugging"
      fi
      exit $install_exit_code
    fi

    # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner)
    echo ""
    echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"

    if read -t 60 -r response; then
      if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
        # Remove container
        echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
        pct stop "$CTID" &>/dev/null || true
        pct destroy "$CTID" &>/dev/null || true
        echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
      elif [[ "$response" =~ ^[Nn]$ ]]; then
        echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}"

        # Dev mode: Setup MOTD/SSH for debugging access to broken container
        if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then
          echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}"
          if pct exec "$CTID" -- bash -c "
            source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func)
            declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true
          " >/dev/null 2>&1; then
            local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
            echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}"
          fi
        fi
      fi
    else
      # Timeout - auto-remove
      echo -e "\n${YW}No response - auto-removing container${CL}"
      echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
      pct stop "$CTID" &>/dev/null || true
      pct destroy "$CTID" &>/dev/null || true
      echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
    fi

    exit $install_exit_code
  fi
}

destroy_lxc() {
  if [[ -z "$CT_ID" ]]; then
    msg_error "No CT_ID found. Nothing to remove."
    return 1
  fi

  # Abort on Ctrl-C / Ctrl-D / ESC
  trap 'echo; msg_error "Aborted by user (SIGINT/SIGQUIT)"; return 130' INT QUIT

  local prompt
  if ! read -rp "Remove this Container? <y/N> " prompt; then
    # read returns non-zero on Ctrl-D/ESC
    msg_error "Aborted input (Ctrl-D/ESC)"
    return 130
  fi

  case "${prompt,,}" in
  y | yes)
    if pct stop "$CT_ID" &>/dev/null && pct destroy "$CT_ID" &>/dev/null; then
      msg_ok "Removed Container $CT_ID"
    else
      msg_error "Failed to remove Container $CT_ID"
      return 1
    fi
    ;;
  "" | n | no)
    msg_custom "ℹ️" "${BL}" "Container was not removed."
    ;;
  *)
    msg_warn "Invalid response. Container was not removed."
    ;;
  esac
}

# ------------------------------------------------------------------------------
# Storage discovery / selection helpers
# ------------------------------------------------------------------------------
resolve_storage_preselect() {
  local class="$1" preselect="$2" required_content=""
  case "$class" in
  template) required_content="vztmpl" ;;
  container) required_content="rootdir" ;;
  *) return 1 ;;
  esac
  [[ -z "$preselect" ]] && return 1
  if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then
    msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)"
    return 1
  fi

  local line total used free
  line="$(pvesm status | awk -v s="$preselect" 'NR>1 && $1==s {print $0}')"
  if [[ -z "$line" ]]; then
    STORAGE_INFO="n/a"
  else
    total="$(awk '{print $4}' <<<"$line")"
    used="$(awk '{print $5}' <<<"$line")"
    free="$(awk '{print $6}' <<<"$line")"
    local total_h used_h free_h
    if command -v numfmt >/dev/null 2>&1; then
      total_h="$(numfmt --to=iec --suffix=B --format %.1f "$total" 2>/dev/null || echo "$total")"
      used_h="$(numfmt --to=iec --suffix=B --format %.1f "$used" 2>/dev/null || echo "$used")"
      free_h="$(numfmt --to=iec --suffix=B --format %.1f "$free" 2>/dev/null || echo "$free")"
      STORAGE_INFO="Free: ${free_h}  Used: ${used_h}"
    else
      STORAGE_INFO="Free: ${free}  Used: ${used}"
    fi
  fi
  STORAGE_RESULT="$preselect"
  return 0
}

fix_gpu_gids() {
  if [[ -z "${GPU_TYPE:-}" ]]; then
    return 0
  fi

  msg_info "Detecting and setting correct GPU group IDs"

  # Get actual GIDs from container
  local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3")
  local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3")

  # Create groups if they don't exist
  if [[ -z "$video_gid" ]]; then
    pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1
    video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3")
    [[ -z "$video_gid" ]] && video_gid="44"
  fi

  if [[ -z "$render_gid" ]]; then
    pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1
    render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3")
    [[ -z "$render_gid" ]] && render_gid="104"
  fi

  # Stop container to update config
  pct stop "$CTID" >/dev/null 2>&1
  sleep 1

  # Update dev entries with correct GIDs
  sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG"
  sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG"

  # Restart container
  pct start "$CTID" >/dev/null 2>&1
  sleep 2

  msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})"

  # For privileged containers: also fix permissions inside container
  if [[ "$CT_TYPE" == "0" ]]; then
    pct exec "$CTID" -- sh -c "
      if [ -d /dev/dri ]; then
        for dev in /dev/dri/*; do
          if [ -e \"\$dev\" ]; then
            case \"\$dev\" in
              *renderD*) chgrp ${render_gid} \"\$dev\" 2>/dev/null || true ;;
              *)         chgrp ${video_gid} \"\$dev\" 2>/dev/null || true ;;
            esac
            chmod 660 \"\$dev\" 2>/dev/null || true
          fi
        done
      fi
    " >/dev/null 2>&1
  fi
}

check_storage_support() {
  local CONTENT="$1" VALID=0
  while IFS= read -r line; do
    local STORAGE_NAME
    STORAGE_NAME=$(awk '{print $1}' <<<"$line")
    [[ -n "$STORAGE_NAME" ]] && VALID=1
  done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1')
  [[ $VALID -eq 1 ]]
}

select_storage() {
  local CLASS=$1 CONTENT CONTENT_LABEL
  case $CLASS in
  container)
    CONTENT='rootdir'
    CONTENT_LABEL='Container'
    ;;
  template)
    CONTENT='vztmpl'
    CONTENT_LABEL='Container template'
    ;;
  iso)
    CONTENT='iso'
    CONTENT_LABEL='ISO image'
    ;;
  images)
    CONTENT='images'
    CONTENT_LABEL='VM Disk image'
    ;;
  backup)
    CONTENT='backup'
    CONTENT_LABEL='Backup'
    ;;
  snippets)
    CONTENT='snippets'
    CONTENT_LABEL='Snippets'
    ;;
  *)
    msg_error "Invalid storage class '$CLASS'"
    return 1
    ;;
  esac

  declare -A STORAGE_MAP
  local -a MENU=()
  local COL_WIDTH=0

  while read -r TAG TYPE _ TOTAL USED FREE _; do
    [[ -n "$TAG" && -n "$TYPE" ]] || continue
    local DISPLAY="${TAG} (${TYPE})"
    local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
    local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
    local INFO="Free: ${FREE_FMT}B  Used: ${USED_FMT}B"
    STORAGE_MAP["$DISPLAY"]="$TAG"
    MENU+=("$DISPLAY" "$INFO" "OFF")
    ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
  done < <(pvesm status -content "$CONTENT" | awk 'NR>1')

  if [[ ${#MENU[@]} -eq 0 ]]; then
    msg_error "No storage found for content type '$CONTENT'."
    return 2
  fi

  if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then
    STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
    STORAGE_INFO="${MENU[1]}"
    return 0
  fi

  local WIDTH=$((COL_WIDTH + 42))
  while true; do
    local DISPLAY_SELECTED
    DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
      --title "Storage Pools" \
      --radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
      16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { exit_script; }

    DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED")
    if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
      whiptail --msgbox "No valid storage selected. Please try again." 8 58
      continue
    fi
    STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}"
    for ((i = 0; i < ${#MENU[@]}; i += 3)); do
      if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then
        STORAGE_INFO="${MENU[$i + 1]}"
        break
      fi
    done
    return 0
  done
}

create_lxc_container() {
  # ------------------------------------------------------------------------------
  # Optional verbose mode (debug tracing)
  # ------------------------------------------------------------------------------
  if [[ "${CREATE_LXC_VERBOSE:-no}" == "yes" ]]; then set -x; fi

  # ------------------------------------------------------------------------------
  # Helpers (dynamic versioning / template parsing)
  # ------------------------------------------------------------------------------
  pkg_ver() { dpkg-query -W -f='${Version}\n' "$1" 2>/dev/null || echo ""; }
  pkg_cand() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}'; }

  ver_ge() { dpkg --compare-versions "$1" ge "$2"; }
  ver_gt() { dpkg --compare-versions "$1" gt "$2"; }
  ver_lt() { dpkg --compare-versions "$1" lt "$2"; }

  # Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => "13.1"
  parse_template_osver() { sed -n 's/.*_\([0-9][0-9]*\(\.[0-9]\+\)\?\)-.*/\1/p' <<<"$1"; }

  # Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create
  # Returns:
  #   0 = no upgrade needed
  #   1 = upgraded (and if do_retry=yes and retry succeeded, creation done)
  #   2 = user declined
  #   3 = upgrade attempted but failed OR retry failed
  offer_lxc_stack_upgrade_and_maybe_retry() {
    local do_retry="${1:-no}" # yes|no
    local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0

    _pvec_i="$(pkg_ver pve-container)"
    _lxcp_i="$(pkg_ver lxc-pve)"
    _pvec_c="$(pkg_cand pve-container)"
    _lxcp_c="$(pkg_cand lxc-pve)"

    if [[ -n "$_pvec_c" && "$_pvec_c" != "none" ]]; then
      ver_gt "$_pvec_c" "${_pvec_i:-0}" && need=1
    fi
    if [[ -n "$_lxcp_c" && "$_lxcp_c" != "none" ]]; then
      ver_gt "$_lxcp_c" "${_lxcp_i:-0}" && need=1
    fi
    if [[ $need -eq 0 ]]; then
      msg_debug "No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)"
      return 0
    fi

    echo
    echo "An update for the Proxmox LXC stack is available:"
    echo "  pve-container: installed=${_pvec_i:-n/a}  candidate=${_pvec_c:-n/a}"
    echo "  lxc-pve     : installed=${_lxcp_i:-n/a}  candidate=${_lxcp_c:-n/a}"
    echo
    read -rp "Do you want to upgrade now? [y/N] " _ans
    case "${_ans,,}" in
    y | yes)
      msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)"
      if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then
        msg_ok "LXC stack upgraded."
        if [[ "$do_retry" == "yes" ]]; then
          msg_info "Retrying container creation after upgrade"
          if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
            msg_ok "Container created successfully after upgrade."
            return 0
          else
            msg_error "pct create still failed after upgrade. See $LOGFILE"
            return 3
          fi
        fi
        return 1
      else
        msg_error "Upgrade failed. Please check APT output."
        return 3
      fi
      ;;
    *) return 2 ;;
    esac
  }

  # ------------------------------------------------------------------------------
  # Required input variables
  # ------------------------------------------------------------------------------
  [[ "${CTID:-}" ]] || {
    msg_error "You need to set 'CTID' variable."
    exit 203
  }
  [[ "${PCT_OSTYPE:-}" ]] || {
    msg_error "You need to set 'PCT_OSTYPE' variable."
    exit 204
  }

  msg_debug "CTID=$CTID"
  msg_debug "PCT_OSTYPE=$PCT_OSTYPE"
  msg_debug "PCT_OSVERSION=${PCT_OSVERSION:-default}"

  # ID checks
  [[ "$CTID" -ge 100 ]] || {
    msg_error "ID cannot be less than 100."
    exit 205
  }
  if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
    echo -e "ID '$CTID' is already in use."
    unset CTID
    msg_error "Cannot use ID that is already in use."
    exit 206
  fi

  # Storage capability check
  check_storage_support "rootdir" || {
    msg_error "No valid storage found for 'rootdir' [Container]"
    exit 1
  }
  check_storage_support "vztmpl" || {
    msg_error "No valid storage found for 'vztmpl' [Template]"
    exit 1
  }

  # Template storage selection
  if resolve_storage_preselect template "${TEMPLATE_STORAGE:-}"; then
    TEMPLATE_STORAGE="$STORAGE_RESULT"
    TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
    msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
  else
    while true; do
      if [[ -z "${var_template_storage:-}" ]]; then
        if select_storage template; then
          TEMPLATE_STORAGE="$STORAGE_RESULT"
          TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
          msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
          break
        fi
      fi
    done
  fi

  # Container storage selection
  if resolve_storage_preselect container "${CONTAINER_STORAGE:-}"; then
    CONTAINER_STORAGE="$STORAGE_RESULT"
    CONTAINER_STORAGE_INFO="$STORAGE_INFO"
    msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
  else
    if [[ -z "${var_container_storage:-}" ]]; then
      if select_storage container; then
        CONTAINER_STORAGE="$STORAGE_RESULT"
        CONTAINER_STORAGE_INFO="$STORAGE_INFO"
        msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
      fi
    fi
  fi

  msg_info "Validating storage '$CONTAINER_STORAGE'"
  STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1)

  case "$STORAGE_TYPE" in
  iscsidirect) exit 212 ;;
  iscsi | zfs) exit 213 ;;
  cephfs) exit 219 ;;
  pbs) exit 224 ;;
  linstor | rbd | nfs | cifs)
    pvesm status -storage "$CONTAINER_STORAGE" &>/dev/null || exit 217
    ;;
  esac

  pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE" || exit 213
  msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated"

  msg_info "Validating template storage '$TEMPLATE_STORAGE'"
  TEMPLATE_TYPE=$(grep -E "^[^:]+: $TEMPLATE_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1)

  if ! pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$TEMPLATE_STORAGE"; then
    msg_warn "Template storage '$TEMPLATE_STORAGE' may not support 'vztmpl'"
  fi
  msg_ok "Template storage '$TEMPLATE_STORAGE' validated"

  # Free space check
  STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
  REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
  [[ "$STORAGE_FREE" -ge "$REQUIRED_KB" ]] || {
    msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G."
    exit 214
  }

  # Cluster quorum (if cluster)
  if [[ -f /etc/pve/corosync.conf ]]; then
    msg_info "Checking cluster quorum"
    if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then
      msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
      exit 210
    fi
    msg_ok "Cluster is quorate"
  fi

  # ------------------------------------------------------------------------------
  # Template discovery & validation
  # ------------------------------------------------------------------------------
  TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
  case "$PCT_OSTYPE" in
  debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;;
  alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;;
  *) TEMPLATE_PATTERN="" ;;
  esac

  msg_info "Searching for template '$TEMPLATE_SEARCH'"

  # Build regex patterns outside awk/grep for clarity
  SEARCH_PATTERN="^${TEMPLATE_SEARCH}"

  mapfile -t LOCAL_TEMPLATES < <(
    pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
      awk -v search="${SEARCH_PATTERN}" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' |
      sed 's|.*/||' | sort -t - -k 2 -V
  )

  pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)."

  msg_ok "Template search completed"

  set +u
  mapfile -t ONLINE_TEMPLATES < <(pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | awk '{print $2}' | grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true)
  set -u

  ONLINE_TEMPLATE=""
  [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}"

  if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then
    count=0
    for idx in "${!ONLINE_TEMPLATES[@]}"; do
      ((count++))
      [[ $count -ge 3 ]] && break
    done
  fi

  if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then
    TEMPLATE="${LOCAL_TEMPLATES[-1]}"
    TEMPLATE_SOURCE="local"
  else
    TEMPLATE="$ONLINE_TEMPLATE"
    TEMPLATE_SOURCE="online"
  fi

  # If still no template, try to find alternatives
  if [[ -z "$TEMPLATE" ]]; then
    echo ""
    echo "[DEBUG] No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..."

    # Get all available versions for this OS type
    mapfile -t AVAILABLE_VERSIONS < <(
      pveam available -section system 2>/dev/null |
        grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' |
        awk -F'\t' '{print $1}' |
        grep "^${PCT_OSTYPE}-" |
        sed -E "s/.*${PCT_OSTYPE}-([0-9]+(\.[0-9]+)?).*/\1/" |
        sort -u -V 2>/dev/null
    )

    if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then
      echo ""
      echo "${BL}Available ${PCT_OSTYPE} versions:${CL}"
      for i in "${!AVAILABLE_VERSIONS[@]}"; do
        echo "  [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}"
      done
      echo ""
      read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or press Enter to cancel: " choice

      if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#AVAILABLE_VERSIONS[@]} ]]; then
        PCT_OSVERSION="${AVAILABLE_VERSIONS[$((choice - 1))]}"
        TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION}"
        SEARCH_PATTERN="^${TEMPLATE_SEARCH}-"

        mapfile -t ONLINE_TEMPLATES < <(
          pveam available -section system 2>/dev/null |
            grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' |
            awk -F'\t' '{print $1}' |
            grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" |
            sort -t - -k 2 -V 2>/dev/null || true
        )

        if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then
          TEMPLATE="${ONLINE_TEMPLATES[-1]}"
          TEMPLATE_SOURCE="online"
        else
          msg_error "No templates available for ${PCT_OSTYPE} ${PCT_OSVERSION}"
          exit 225
        fi
      else
        msg_custom "🚫" "${YW}" "Installation cancelled"
        exit 0
      fi
    else
      msg_error "No ${PCT_OSTYPE} templates available at all"
      exit 225
    fi
  fi

  TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
  if [[ -z "$TEMPLATE_PATH" ]]; then
    TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
    [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
  fi

  # If we still don't have a path but have a valid template name, construct it
  if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then
    TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
  fi

  [[ -n "$TEMPLATE_PATH" ]] || {
    if [[ -z "$TEMPLATE" ]]; then
      msg_error "Template ${PCT_OSTYPE} ${PCT_OSVERSION} not available"

      # Get available versions
      mapfile -t AVAILABLE_VERSIONS < <(
        pveam available -section system 2>/dev/null |
          grep "^${PCT_OSTYPE}-" |
          sed -E 's/.*'"${PCT_OSTYPE}"'-([0-9]+\.[0-9]+).*/\1/' |
          grep -E '^[0-9]+\.[0-9]+$' |
          sort -u -V 2>/dev/null || sort -u
      )

      if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then
        echo -e "\n${BL}Available versions:${CL}"
        for i in "${!AVAILABLE_VERSIONS[@]}"; do
          echo "  [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}"
        done

        echo ""
        read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or Enter to exit: " choice

        if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#AVAILABLE_VERSIONS[@]} ]]; then
          export var_version="${AVAILABLE_VERSIONS[$((choice - 1))]}"
          export PCT_OSVERSION="$var_version"
          msg_ok "Switched to ${PCT_OSTYPE} ${var_version}"

          # Retry template search with new version
          TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
          SEARCH_PATTERN="^${TEMPLATE_SEARCH}-"

          mapfile -t LOCAL_TEMPLATES < <(
            pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
              awk -v search="${SEARCH_PATTERN}" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' |
              sed 's|.*/||' | sort -t - -k 2 -V
          )
          mapfile -t ONLINE_TEMPLATES < <(
            pveam available -section system 2>/dev/null |
              grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' |
              awk -F'\t' '{print $1}' |
              grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" |
              sort -t - -k 2 -V 2>/dev/null || true
          )
          ONLINE_TEMPLATE=""
          [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}"

          if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then
            TEMPLATE="${LOCAL_TEMPLATES[-1]}"
            TEMPLATE_SOURCE="local"
          else
            TEMPLATE="$ONLINE_TEMPLATE"
            TEMPLATE_SOURCE="online"
          fi

          TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
          if [[ -z "$TEMPLATE_PATH" ]]; then
            TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
            [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
          fi

          # If we still don't have a path but have a valid template name, construct it
          if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then
            TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
          fi

          [[ -n "$TEMPLATE_PATH" ]] || {
            msg_error "Template still not found after version change"
            exit 220
          }
        else
          msg_custom "🚫" "${YW}" "Installation cancelled"
          exit 1
        fi
      else
        msg_error "No ${PCT_OSTYPE} templates available"
        exit 220
      fi
    fi
  }

  # Validate that we found a template
  if [[ -z "$TEMPLATE" ]]; then
    msg_error "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}"
    msg_custom "ℹ️" "${YW}" "Please check:"
    msg_custom "  •" "${YW}" "Is pveam catalog available? (run: pveam available -section system)"
    msg_custom "  •" "${YW}" "Does the template exist for your OS version?"
    exit 225
  fi

  msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]"
  msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH"

  NEED_DOWNLOAD=0
  if [[ ! -f "$TEMPLATE_PATH" ]]; then
    msg_info "Template not present locally – will download."
    NEED_DOWNLOAD=1
  elif [[ ! -r "$TEMPLATE_PATH" ]]; then
    msg_error "Template file exists but is not readable – check permissions."
    exit 221
  elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
    if [[ -n "$ONLINE_TEMPLATE" ]]; then
      msg_warn "Template file too small (<1MB) – re-downloading."
      NEED_DOWNLOAD=1
    else
      msg_warn "Template looks too small, but no online version exists. Keeping local file."
    fi
  elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
    if [[ -n "$ONLINE_TEMPLATE" ]]; then
      msg_warn "Template appears corrupted – re-downloading."
      NEED_DOWNLOAD=1
    else
      msg_warn "Template appears corrupted, but no online version exists. Keeping local file."
    fi
  else
    $STD msg_ok "Template $TEMPLATE is present and valid."
  fi

  if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then
    msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)"
    if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then
      TEMPLATE="$ONLINE_TEMPLATE"
      NEED_DOWNLOAD=1
    else
      msg_custom "ℹ️" "${BL}" "Continuing with local template $TEMPLATE"
    fi
  fi

  if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then
    [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH"
    for attempt in {1..3}; do
      msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE"
      if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then
        msg_ok "Template download successful."
        break
      fi
      if [[ $attempt -eq 3 ]]; then
        msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n  pveam download $TEMPLATE_STORAGE $TEMPLATE"
        exit 222
      fi
      sleep $((attempt * 5))
    done
  fi

  if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then
    msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download."
    exit 223
  fi

  # ------------------------------------------------------------------------------
  # Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins)
  # ------------------------------------------------------------------------------
  if [[ "$PCT_OSTYPE" == "debian" ]]; then
    OSVER="$(parse_template_osver "$TEMPLATE")"
    if [[ -n "$OSVER" ]]; then
      offer_lxc_stack_upgrade_and_maybe_retry "no" || true
    fi
  fi

  # ------------------------------------------------------------------------------
  # Create LXC Container
  # ------------------------------------------------------------------------------
  msg_info "Creating LXC container"

  # Ensure subuid/subgid entries exist
  grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
  grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid

  # PCT_OPTIONS is now a string (exported from build_container)
  # Add rootfs if not already specified
  if [[ ! "$PCT_OPTIONS" =~ "-rootfs" ]]; then
    PCT_OPTIONS="$PCT_OPTIONS
  -rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}"
  fi

  # Lock by template file (avoid concurrent downloads/creates)
  lockfile="/tmp/template.${TEMPLATE}.lock"
  exec 9>"$lockfile" || {
    msg_error "Failed to create lock file '$lockfile'."
    exit 200
  }
  flock -w 60 9 || {
    msg_error "Timeout while waiting for template lock."
    exit 211
  }

  LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log"

  msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS"
  msg_debug "Logfile: $LOGFILE"

  # First attempt (PCT_OPTIONS is a multi-line string, use it directly)
  if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then
    msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..."

    # Validate template file
    if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
      msg_warn "Template file too small or missing – re-downloading."
      rm -f "$TEMPLATE_PATH"
      pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
    elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
      if [[ -n "$ONLINE_TEMPLATE" ]]; then
        msg_warn "Template appears corrupted – re-downloading."
        rm -f "$TEMPLATE_PATH"
        pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
      else
        msg_warn "Template appears corrupted, but no online version exists. Skipping re-download."
      fi
    fi

    # Retry after repair
    if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
      # Fallback to local storage if not already on local
      if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
        msg_info "Retrying container creation with fallback to local storage..."
        LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
        if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then
          msg_info "Downloading template to local..."
          pveam download local "$TEMPLATE" >/dev/null 2>&1
        fi
        if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
          # Local fallback also failed - check for LXC stack version issue
          if grep -qiE 'unsupported .* version' "$LOGFILE"; then
            echo
            echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
            echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
            offer_lxc_stack_upgrade_and_maybe_retry "yes"
            rc=$?
            case $rc in
            0) : ;; # success - container created, continue
            2)
              echo "Upgrade was declined. Please update and re-run:
  apt update && apt install --only-upgrade pve-container lxc-pve"
              exit 231
              ;;
            3)
              echo "Upgrade and/or retry failed. Please inspect: $LOGFILE"
              exit 231
              ;;
            esac
          else
            msg_error "Container creation failed. See $LOGFILE"
            if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
              set -x
              pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
              set +x
            fi
            exit 209
          fi
        else
          msg_ok "Container successfully created using local fallback."
        fi
      else
        # Already on local storage and still failed - check LXC stack version
        if grep -qiE 'unsupported .* version' "$LOGFILE"; then
          echo
          echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
          echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
          offer_lxc_stack_upgrade_and_maybe_retry "yes"
          rc=$?
          case $rc in
          0) : ;; # success - container created, continue
          2)
            echo "Upgrade was declined. Please update and re-run:
  apt update && apt install --only-upgrade pve-container lxc-pve"
            exit 231
            ;;
          3)
            echo "Upgrade and/or retry failed. Please inspect: $LOGFILE"
            exit 231
            ;;
          esac
        else
          msg_error "Container creation failed. See $LOGFILE"
          if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
            set -x
            pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
            set +x
          fi
          exit 209
        fi
      fi
    else
      msg_ok "Container successfully created after template repair."
    fi
  fi

  # Verify container exists
  pct list | awk '{print $1}' | grep -qx "$CTID" || {
    msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE"
    exit 215
  }

  # Verify config rootfs
  grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || {
    msg_error "RootFS entry missing in container config. See $LOGFILE"
    exit 216
  }

  msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."

  # Report container creation to API
  post_to_api
}

# ==============================================================================
# SECTION 9: POST-INSTALLATION & FINALIZATION
# ==============================================================================

# ------------------------------------------------------------------------------
# description()
#
# - Sets container description with formatted HTML content
# - Includes:
#   * Community-Scripts logo
#   * Application name
#   * Links to GitHub, Discussions, Issues
#   * Ko-fi donation badge
# - Restarts ping-instances.service if present (monitoring)
# - Posts final "done" status to API telemetry
# ------------------------------------------------------------------------------
description() {
  IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)

  # Generate LXC Description
  DESCRIPTION=$(
    cat <<EOF
<div align='center'>
  <a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
    <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
  </a>

  <h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>

  <p style='margin: 16px 0;'>
    <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
      <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
    </a>
  </p>

  <span style='margin: 0 10px;'>
    <i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
    <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
  </span>
  <span style='margin: 0 10px;'>
    <i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
    <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
  </span>
  <span style='margin: 0 10px;'>
    <i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
    <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
  </span>
</div>
EOF
  )
  pct set "$CTID" -description "$DESCRIPTION"

  if [[ -f /etc/systemd/system/ping-instances.service ]]; then
    systemctl start ping-instances.service
  fi

  post_update_to_api "done" "none"
}

# ==============================================================================
# SECTION 10: ERROR HANDLING & EXIT TRAPS
# ==============================================================================

# ------------------------------------------------------------------------------
# api_exit_script()
#
# - Exit trap handler for reporting to API telemetry
# - Captures exit code and reports to API using centralized error descriptions
# - Uses explain_exit_code() from error_handler.func for consistent error messages
# - Posts failure status with exit code to API (error description added automatically)
# - Only executes on non-zero exit codes
# ------------------------------------------------------------------------------
api_exit_script() {
  exit_code=$?
  if [ $exit_code -ne 0 ]; then
    post_update_to_api "failed" "$exit_code"
  fi
}

if command -v pveversion >/dev/null 2>&1; then
  trap 'api_exit_script' EXIT
fi
trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR
trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
